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