使用带有索引的包扩展 - 它是 UB 吗?

Using a pack expansion with an index - is it UB?

下面的代码“似乎”有效 - 但是,我有点担心我在标记点处于未指定行为的领域。如果我是,有人可以给我一个骨头,这样我就可以确保我不会在更改编译器时突然崩溃吗?

我的意图(以防万一)是我想生成一个能够包装另一个的 std::function - 但以稍微不同的方式处理参数。

/// Some collection of arguments generated at runtime.
class ArgCollection 
{
   int argCount;
   std::variant *arguments;
}

/// generate the wrapping fn
template<class ...Args>
std::function<void(ArgCollection)> GetConvert(std::function<void(Args...)> thing)
{
    constexpr std::size_t argCount = sizeof...(Args);
    return [argCount, method](const ArgCollection& args) -> void {
        if (args.numArguments != argCount)
            throw std::invalid_argument("Invalid number of arguments");

        int count = 0;  <------------ I fear about the usage of this variable.
        auto convertedArgs = std::make_tuple(ConvertArg<Args>(args, count++)...);
        std::apply(method, convertedArgs);
    };
}

/// helper const & reference stripping
template<typename T>
using base_type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;

/// Get the idx'th argument, and convert it to what we can hand to the function
template<class T>
static base_type<T> ConvertArg(const ArgCollection &args, int idx)
{
    return base_type<T>(args[idx]);
}
auto convertedArgs = std::make_tuple(ConvertArg<Args>(args, count++)...);

增量相对于彼此的顺序是不确定的。编译器可以自由地以任何顺序执行它们,并且可以因为蝴蝶扇动翅膀而改变顺序。 (在 之前,比这更糟糕的保证)

中有一个简单的解决方法:

constexpr std::size_t argCount = sizeof...(Args);
return [&]<std::size_t...Is>(std::index_sequence<Is...>){
  return [argCount, method](const ArgCollection& args) -> void {
    if (args.numArguments != argCount)
        throw std::invalid_argument("Invalid number of arguments");

    auto convertedArgs = std::make_tuple(ConvertArg<Args>(args, Is)...);
    std::apply(method, convertedArgs);
  };
}( std::make_index_sequence<sizeof...(Args)>{} );

我们创建一个索引序列对象并将其解压缩到函数内的 lambda 中。

中,您基本上需要构建和解压缩索引的辅助函数。

template<auto x>
using constant_t = std::integral_constant<std::decay_t<decltype(x)>, x>;
template<auto x>
constexpr constant_t<x> constant_v={};

template<std::size_t...Is, class F>
decltype(auto) index_over( std::index_sequence<Is...>, F&& f ) {
  return f( constant_v<Is>... );
}
template<std::size_t N, class F>
decltype(auto) index_upto(F&& f) {
  return index_over( std::make_index_sequence<N>{}, std::forward<F>(f) );
}

那么你的代码就变成了:

constexpr std::size_t argCount = sizeof...(Args);
return index_upto<argCount>([&](auto...Is){
  return [argCount, method, Is...](const ArgCollection& args) -> void {
    if (args.numArguments != argCount)
        throw std::invalid_argument("Invalid number of arguments");

    auto convertedArgs = std::make_tuple(ConvertArg<Args>(args, Is)...);
    std::apply(method, convertedArgs);
  };
});

或类似的东西。

您还可以编写一个更常规的辅助函数,将索引序列传递给该函数。

最后,您可以相信基于 {} 的初始化是有序的。

template<class ...Args>
std::function<void(ArgCollection)> GetConvert(std::function<void(Args...)> thing)
{
    constexpr std::size_t argCount = sizeof...(Args);
    return [argCount, method](const ArgCollection& args) -> void {
        if (args.numArguments != argCount)
            throw std::invalid_argument("Invalid number of arguments");

        int count = 0;  <------------ I fear about the usage of this variable.
        auto convertedArgs = std::tuple{ConvertArg<Args>(args, count++)...};
        std::apply(method, convertedArgs);
    };
}

这可能更容易。