Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

The code below "seems" to work - however, I'm a little concerned that I'm in the realms of unspecified behaviour at the marked point. If I am, can someone please throw me a bone so that I can ensure that I'm not going to have it suddenly break when I change compiler?

The intent (in case it isn't clear) is that I want to generate a std::function that is able to wrap another - but process the arguments in a slightly different way.

/// 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]);
}
like image 419
UKMonkey Avatar asked Oct 20 '25 13:10

UKMonkey


1 Answers

auto convertedArgs = std::make_tuple(ConvertArg<Args>(args, count++)...);

the increments are indeterminately sequenced relative to each other. Compilers are free to do them in any order, and change the order because a butterfly flaps its wings. (Prior to c++17 the guarantees where worse than this)

In c++20 there is an easy work around:

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)>{} );

where we make an index sequence object and unpack it in a lambda within the function.

In c++17 you basically need helper functions that build and unpack the indexes.

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) );
}

then your code becomes:

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);
  };
});

or somesuch.

You can also write a more conventional helper function that you pass an index sequence to.

Finally, you can rely on the fact that {} based initialization is ordered.

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);
    };
}

which could be easier.

like image 85
Yakk - Adam Nevraumont Avatar answered Oct 23 '25 03:10

Yakk - Adam Nevraumont