1

I have written the following short code in C++11 of a variable template function and store the arguments into a vector of type boost::any. It is working perfectly, but I don't want to use the boost::any library (due to some limitation).

#include <boost/any.hpp>

template <class Var, class... Args>
void cpp_for(Var *variable, uint32_t numParams, Args... args)
{
    std::vector<boost::any> arguments{args...};

    if(arguments.size() != numParams)
        throw std::runtime_error("mismatch");

    for(uint32_t i = 0; i < numParams; ++i)
        variable[i] = *(boost::unsafe_any_cast<Var>(&arguments[i]));
}

And I call the function like this:

cpp_for(myObj->var, 3, 0x56, 0x23, 0x10);

Or

cpp_for(myObj2->var, 2, myObj2->var2, myObj2->var3);

Is there any way to store the arguments and process them one by one without the need for boost::any?

Edit 1: my arguments are all of the same type.

Edit 2: Since the goal of the code above is assignment, then creating an extra data structure (vector) is useless. Check 'Nir Friedman''s answer for a more efficient solution.

ManiAm
  • 1,759
  • 5
  • 24
  • 43
  • Possible duplicate of [Getting the type of passed argument in variadic function](https://stackoverflow.com/questions/3564061/getting-the-type-of-passed-argument-in-variadic-function) – erip Aug 11 '17 at 20:01
  • 1
    It is pretty totally unclear what this function is actually trying to accomplish. Like, why bother creating a vector at all? Why are you specifying the number of parameters by hand? – Nir Friedman Aug 11 '17 at 20:02
  • I think the `sizeof...` operator may help you – AndyG Aug 11 '17 at 20:04
  • Using a function name of all caps confuses it with preprocessor functions. – CodeLurker Aug 11 '17 at 20:17
  • @CodeLurker: Initially I was looking for a replacement for P99_FOR in C++11 and CPP_FOR is an attempt to replicate it (somehow), but I forgot to change the name. – ManiAm Aug 11 '17 at 20:31

3 Answers3

5

You could use std::common_type, e.g.:

template <class Var, class... Args>
void CPP_FOR(Var *variable, uint32_t numParams, Args... args)
{
    std::vector<std::common_type_t<Args...>> arguments{args...};
    // do stuff with arguments
}

You can also remove numParams and the runtime check because this will fail at compile time if there is no common type. And if you only want to iterate over the arguments, a vector is maybe overkill... so something like:

template <class Var, class... Args>
void CPP_FOR(Var *variable, Args... args)
{
    std::common_type_t<Args...> arguments[]{args...};
    for(size_t i = 0; i < sizeof...(Args); ++i)
        variable[i] = /* ... */;
}

Note that both of these will fails if sizeof... (Args) is 0, i.e. you are calling with only a Var* - You may want to handle this case separately if necessary.

Holt
  • 36,600
  • 7
  • 92
  • 139
  • This is probably a better approach than my answer (was not aware of `std::common_type`). I suggest adding a note about the case where `sizeof...(Args)` is zero. – Silvio Mayolo Aug 11 '17 at 20:07
  • If the goal is assignment, then creating any kind of data structure from the arguments is needless and inefficient. you can just assign them directly as in my answer. – Nir Friedman Aug 11 '17 at 20:12
3

Assuming that your goal is really just to perform assignments, you don't need a vector at all.

template <class Var, class... Args>
void CPP_FOR(Var *variable, uint32_t numParams, Args... args)
{    
    if(sizeof...(Args) != numParams)
        throw std::runtime_error("mismatch");

    int i = 0;
    int temp [] = {(variable[i++] = args, 0)...};
}

Live example: http://coliru.stacked-crooked.com/a/710a09332bf2c965

Not only is this zero overhead compared to creating a vector that is hard to optimize away, but it will allow implicit conversions in the most natural way. Other approaches may have surprises.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • Is there a guarantee regarding the evaluation order of the `i++` in this pack expansion? – Holt Aug 11 '17 at 20:12
  • Packs have to be expanded left to right, if that's what you are asking. – Nir Friedman Aug 11 '17 at 20:13
  • 1
    Expanded, yes. But evaluated, no. I don't think the order is well-defined. Have you tried running this? – Silvio Mayolo Aug 11 '17 at 20:13
  • @SilvioMayolo evaluated too. You are getting confused with function argument evaluation order. Brace packs have to be evaluated left to right. I provide a live link right in my answer... – Nir Friedman Aug 11 '17 at 20:14
  • 1
    @Holt E.g. see https://stackoverflow.com/questions/14060264/order-of-evaluation-of-elements-in-list-initialization – Nir Friedman Aug 11 '17 at 20:16
  • I think we can remove the 'numParams' check altogether, too. You beat me to the array expansion! And it's simpler. [I used an index sequence](https://wandbox.org/permlink/wuH6so8nmshLbiUW), which I thought was nice, but those weren't standardized until C++14, either. – AndyG Aug 11 '17 at 20:29
  • @AndyG Yeah, the annoying thing with index sequences is that they always require calling out to another function. This trick is nice for quick and dirty solutions. Though, IMHO the real way to go is to have a small library can apply variadic lambdas to tuples/packs, and conceal this array trick/integer sequence behind that. You can have a version that passes in the integer to the lambda as well. – Nir Friedman Aug 11 '17 at 20:42
1

If you know that there's going to be at least one argument, you can write your function slightly differently to do so.

template <typename Arg, typename... Args>
void cpp_for(Arg arg, Args... args) {
  std::vector<Arg> vec { arg, args... };
  // ...
}

However, this will fail if the argument list is empty. The easiest solution to this is simply to add a second overload of cpp_for which takes no arguments.

void cpp_for() {
  std::vector<SomeDefaultType> vec; // Empty vector
  // ...
}

Of course, you only need to do so if it makes sense to call your function with zero arguments.

Bear in mind that this approach will give positively miserable error messages if Args and Arg don't all end up being the same type. This can be remedied with some careful use of static_assert.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116