tuple foreach, index, find: return 运行时的编译时常量

tuple foreach, index, find: return compile-time constant during runtime

元组 foreach 使用递归或 std::apply:

相对简单
#include <cstring>
#include <iostream>
#include <tuple>
#include <utility>

template<typename F, typename T>
auto foreach_apply(F&& f, T &&t) {
    return std::apply([&f](auto&&... elements) {
        return (f(std::forward<decltype(elements)>(elements)) || ...);
    }, std::forward<T>(t));
}

template <std::size_t I=0, typename F, typename... Ts>
void foreach_recurse(F&& f, std::tuple<Ts...> t) {
    if constexpr (I == sizeof...(Ts)) {
        return;
    } else {
        f(std::get<I>(t));
        find<I+1>(f, t);
    }
}

int main() {
   //auto a = std::tuple(true, true, false, false, true);
   auto a = std::tuple("one", "two", "three", "four", "five");
   //auto b = foreach_recurse([](auto& element) -> bool {
   auto b = foreach_apply([](auto& element) -> bool {
       std::cout << element << std::endl;
       if (!strcmp("three", element))
           return true;
       else
           return false;
   }, a);
   std::cout << "Done" << std::endl;
   std::cout << b << std::endl << std::endl;
}

索引只是稍微有点棘手:

template <std::size_t I=0, typename F, typename... Ts>
size_t index_recurse(F&& f, std::tuple<Ts...> t) {
    if constexpr (I == sizeof...(Ts)) {
        return -1;
    } else {
        auto e = std::get<I>(t);
        if (f(e))
            return I;
        return index_recurse<I+1>(f, t);
    }
}

template <std::size_t I=0, typename F, typename... Ts>
bool index_recurse_2(F&& f, std::tuple<Ts...> t, size_t* i) {
    if constexpr (I == sizeof...(Ts)) {
        return false;
    } else {
        auto e = std::get<I>(t);
        if (f(e)) {
            *i = I;
            return true;
        }
        return index_recurse_2<I+1>(f, t, i);
    }
}

template<typename F, typename T>
auto index_apply(F&& f, T &&t, size_t* ix) {
    return std::apply([&f,ix] (auto&&... elements) {

        return [&f,ix]<std::size_t... I>(std::index_sequence<I...>, auto&&... elements) {
             
             auto fi = [&f,ix](auto i, auto&& element) {
                 auto r = f(std::forward<decltype(element)>(element));
                 if (r)
                     *ix = i;
                 return r;
             };
             return (fi(I, std::forward<decltype(elements)>(elements)) || ...);

        }
         ( std::make_index_sequence<sizeof...(elements)>()
         , std::forward<decltype(elements)>(elements)...
         );

    }, std::forward<T>(t));
}

int main() {
   /*
   auto a = std::tuple("one", "two", "three", "four", "five");
   auto b = index_recurse([](auto& element) -> bool {
       std::cout << element << std::endl;
       if (!strcmp("three", element))
           return true;
       else
           return false;
   }, a);
   std::cout << "Done" << std::endl;
   std::cout << b << std::endl << std::endl;
   */

   /*
   auto a = std::tuple("one", "two", "three", "four", "five");
   size_t b;
   auto c = index_recurse_2([](auto& element) -> bool {
       std::cout << element << std::endl;
       if (!strcmp("three", element))
           return true;
       else
           return false;
   }, a, &b);
   std::cout << "Done" << std::endl;
   std::cout << b << std::endl << std::endl;
   */

   /*
   auto a = std::tuple("one", "two", "three", "four", "five");
   size_t b;
   auto c = index_apply([](auto& element) -> bool {
       std::cout << element << std::endl;
       if (!strcmp("three", element))
           return true;
       else
           return false;
   }, a, &b);
   std::cout << "Done" << std::endl;
   std::cout << b << std::endl << std::endl;
   */
}

现在,获取值而不是索引。 index_* 函数表明这是可能的。我们采用编译时值(元组)和一组编译时函数(展开的索引函数),应用匹配输入的运行时函数,并获得依赖于编译时值的运行时值。这就是 find_* 函数试图做的事情。

给定一个长度为 N 的元组 T,find_broken_1 展开为 N 个类型为 (i:0-N, T) 的函数,其中每个函数 return 是第 i 个函数,或者从序列中的下一个开始 returns。这意味着第 i 个函数的 return 类型必须匹配所有先前的 return 类型。所以这种递归的方式是行不通的。

template <std::size_t I=0, typename F, typename... Ts>
auto* find_broken_1(F&& f, std::tuple<Ts...> t) {
    if constexpr (I == sizeof...(Ts)) {
        // What type? Can this be fixed?
        return (const char**)&"<>";
        //return (void*)nullptr;
        //return ((decltype(std::get<I-1>)::type)*)nullptr;
    } else {
        auto& e = std::get<I>(t);
        if (f(e))
            return &e;
        std::cout << e << std::endl;
        return find_broken_1<I+1>(f, t);
    }
}

此处索引在编译时未知,因此 std::get 无法编译。如果 C++ 可以为每个可能的索引创建模板,那么这将在运行时工作,就像 C++ 已经展开每个可能的索引函数一样。

template <typename F, typename T>
auto find_broken_2(F&& f, T&& t, bool* b) {
    const size_t i = index_recurse(f, t);
    if (i >= 0) {
        *b = true;
        return std::get<i>(t);
    }
    else {
        *b = false;
        //return nullptr;
    }
}

我们知道我们可以从编译时值生成运行时值。如果转换是可逆的并且边界是已知的,我们应该能够从运行时值中查找编译时值。我们怎样才能欺骗编译器这样做呢?然后将其集成到 index_* 函数中以避免重复迭代。

将某些东西移动到一个类型中会强制它成为编译时的东西。不幸的是,这会为每个生成的函数展开不同的 return 类型,并且由于 return 是链接的,因此会产生编译错误。递归方法再次失败。

我只是在尝试一些不同的想法,none 工作:

template<bool value, typename ...>
struct bool_type : std::integral_constant<bool , value> {};

//std::integral_constant<int, I> run_to_compile(size_t i, std::tuple<Ts...> t) {
template <std::size_t I=0, typename... Ts>
auto run_to_compile(size_t i, std::tuple<Ts...> t) {
    if constexpr (I == sizeof...(Ts)) {
        return std::integral_constant<int, I-1>();
        // replace with static_assert... nope
        //static_assert(bool_type<false, Ts...>::value, "wrong");
        //return I-1;
    }
    else {
        //if (i == I) {
        if (I > 2) {
            //return;
            return std::integral_constant<int, I>();
            //return I;
        }
        return run_to_compile<I+1>(i, t);
    }
}

template<int value, typename ...>
struct int_type : std::integral_constant<int , value> {};

template <int I=0, typename T>
constexpr auto run_to_compile_2(int i) {
    return int_type<i, T>();
}

template <typename F, typename T>
auto find_broken_3(F&& f, T&& t, bool* b) {
    const size_t ir = index_recurse(f, t);
    // nope
    //constexpr decltype(t) temp = {};
    // nope, again (really?)
    //auto ic = std::integral_constant<int, ir>();
    //constexpr auto ic = run_to_compile(0, temp);
    //const auto ic = run_to_compile(ir, t);
    //const auto ic = r2c_2<const int>(ir);
    run_to_compile_2<2, int>(2);
    const auto ic = 2;
    if (ir >= 0) {
        *b = true;
        return std::get<ic>(t);
    }
    else {
        *b = false;
        //return nullptr;
    }
}

我该如何解决这个问题?

Return 类型不能依赖于运行时。所以统一 return 类型的可能性是 std::variant:

template <typename F, typename... Ts>
auto find_in_tuple(F&& f, std::tuple<Ts...> t)
{
    std::optional<std::variant<Ts...>> res;
    std::apply([&](auto&&... args){
        auto lambda = [&](auto&& arg){
            if (!res && f(arg))
                res = arg;
        };
        (lambda(args), ...);
    }, t);
    return res;
}

Demo