0

Is there any way to do initializer list folding as opposed to using a parameter pack? My issue is that I have a heavily overloaded constructor, and I want to invoke different constructors based on whether I use {} or not. This seems to work fine with a initializer list, which manages to hide my other one argument constructor when I use {} as opposed to when I just construct it with (), but fails if I use a parameter pack which doesn't hide my other one argument constructor.

Also on a side note, I've seen people append void in their folding expressions which I couldn't make any sense of when I referred to cppreference, nor does it seem to make any difference in my program.

Edit: As requested, an example to illustrate the problem:

#include <iostream>
#define USE_PARAMETER_PACK false

template<typename T>
struct Mega
{
    int d;
    T* arr;
    Mega(int d) : d(d), arr(new T[d]) {}

    Mega(int d, T u) : d(d), arr(new T[d])
    {
        std::fill(arr, arr + d, static_cast<T>(u));
    }
#if USE_PARAMETER_PACK == true
    template<typename ...Ts>
    Mega(Ts&& ... vals) : d(sizeof...(Ts)), arr(new T[sizeof...(Ts)])
    {
        // fills the array with the arguments at compile time
        int i = 0;
        (void(arr[i++] = static_cast<T>(vals)), ...);
    }
#else
    template<typename U>
    Mega(const std::initializer_list<U>& list) : d(list.size()), arr(new T[d])
    {
        auto it = list.begin();
        //int i = 0;
        //((arr[i++] = (list)), ...);
        for (size_t i = 0; i < d; ++i, ++it)
            arr[i] = static_cast<T>(*it);
    }
#endif
};

template<typename T>
std::ostream& operator<<(std::ostream& os, const Mega<T>& m)
{
    for (size_t i = 0; i < m.d; ++i)
        os << m.arr[i] << "\t";
    return os;
}

int main()
{
    int* k;
    k = new int[2];
    k[0] = 2;
    k[1] = 3;
    Mega<int> l( k[0] );
    // hides 1 argument ctor through {} invocation if using initializer_list,
    // not so with parameter pack
    Mega<int> m({ k[0]}); 
    Mega<int> n(k[0], k[1]);
    // hides 2 argument ctor through {} invocation if using initializer list
    // not so with parameter pack
    Mega<int> o({ k[0], k[1] }); 
    std::cout << l << "\n";
    std::cout << m << "\n";
    std::cout << n << "\n";
    std::cout << o << "\n";

    return 0;
}

Note the commented out part, I would've liked to be able to do something like this in order for the filling out process of known size parameter lists to be figured out at compile time, rather than me using a for loop. Should print out some garbage values for the first cout, and 2 for the second (at least in MSVC2017 it does, no idea whether this hiding mechanism conforms to the standard). Note that if you set the define to true you can use the parameter pack ctor, but it fails to hide the one argument ctor even with {} syntax.

Edit2: Further updated the code for maximum convenience, just change the define to true in order to see that the parameter pack fails to hide the 1 and 2 argument constructors with {} syntax, whereas the initializer list ctor manages to.

Links: Using initializer list: http://coliru.stacked-crooked.com/a/7b876e1dfbb18d73 Output:

0   0   

2   

3   3   

2   3   

Using parameter pack: http://coliru.stacked-crooked.com/a/11042b2fc45b5259 Output:

0   0   

0   0   

3   3   

3   3   
lightxbulb
  • 1,251
  • 12
  • 29
  • Someone might be able help you if you post a [mcve]. – R Sahu Apr 07 '19 at 20:42
  • What do you mean by "initializer list folding"? – Barry Apr 07 '19 at 20:43
  • @Barry Being able to fold an initializer list the same way I would a parameter pack. – lightxbulb Apr 07 '19 at 20:46
  • 3
    The void term in a fold is to prohibit user-defined operator overloads (esp. the comma operator) from being invoked, as parameters to a function cannot be cv-qualified void. Probably not an issue for most users, but necessary for proper, safe library code. – Cruz Jean Apr 07 '19 at 20:51
  • *"I would've liked to be able to do something like this in order for the ... process ... to be figured out at compile time"* I wouldn't worry about it. Modern compilers should be smart enough to unroll the loop. – HolyBlackCat Apr 07 '19 at 22:08
  • Why don't you use std::tuple<> ?? – Michaël Roy Apr 08 '19 at 02:07
  • @MichaëlRoy Could you elaborate on how that will help with this case? Will ```{}``` syntax hide the other ctors with a tuple ctor? – lightxbulb Apr 08 '19 at 07:00
  • No, my comment has more to do with the elimination of the loop. Tuples can be built from an initializer list. The net result would be the same, but with bonus unpacking provided by the stl. Aside notes: you really should make your one argument ctor explicit. And why don't you use std::vector ??? Avoid direct calls to new and delete, whenever possible.It will also make your life a whole lot easier – Michaël Roy Apr 08 '19 at 07:27
  • @MichaëlRoy Oh ok, I got you. I know how to write a recursive template for, for init lists, was just wondering whether folding works, which I guess doesn't based on the answers. Also why should I disallow implicit argument conversions, especially if the types are compatible? I don't use std::vector, because the rest of the code, that you don't see here, does things that std::vector cannot. – lightxbulb Apr 08 '19 at 08:07
  • non-explicit one-argument constructors are bugs waiting to happen. And I've been doing this long enough to assure you there is no operation you can do on a array that you cannot do with a vector. The goal is to eliminate potential bugs. Good coding practices are all about avoiding headaches later – Michaël Roy Apr 08 '19 at 09:39
  • @MichaëlRoy Yeah,I should probably make the one argument ctor,rather than explicit, accept only unsigned integer types, and also have an assert for 0 as an argument. On the other hand I must non explicit argument ctors are not always a bug, consider a math vector class (fixed dimension) that can be initialize from scalar (all coordinates become equal to it), it shouldn't be explicit for convenience (look at glsl or hlsl for example). As far as using vector goes, there's too much extra attached to it that is unnecessary in my case, and it actually complicates things a bit for my specific class. – lightxbulb Apr 08 '19 at 13:17
  • One argument constructors are tricky. The rule of thumb is to declare them explicit if the two types are not expressing the same idea. For example, a constructor taking a std::vector (hint to your problem) would not need to to be explicit. But a non-explicit ctor taking in a single int is dangerous, and may also be confusing at the call point. `my_mega = some_int;`can be a bit confusing, while `my_mega = Mega{some_int};` `can never be. Look for this good lecture that touches on the subject ; "CppCon 2018: Titus Winters “Modern C++ Design" – Michaël Roy Apr 08 '19 at 19:10
  • hint continued.... By having a ctor accepting an std vector *by value*, your construction site would become `auto my_mega = Mega{{1, 2, 3}},` , And if you had a vector to hold your object data, you may even be able (depending on the type help in the input vector) to call the move constructor of your internal vector with the user-supplied one, thus saving a memory allocation. – Michaël Roy Apr 09 '19 at 18:52
  • I'll look into using a std::vector, I was working on a fully static implementation (with static dimension), and pretty much managed to implement all of the operations as constexpr. – lightxbulb Apr 09 '19 at 20:01
  • 1
    Initializer lists have a size chosen at runtime; folding them is a matter for a loop, not the template processor. (I can’t quite tell if this is the *answer* to more than just the question title.) – Davis Herring May 02 '19 at 02:28
  • @DavisHerring That's a good answer really, it confirms that there's simply no way to do static folding on those. – lightxbulb May 02 '19 at 04:57
  • @lightxbulb: It doesn’t address whatever constructor hiding issue, but I’ll post it as an answer if you like. – Davis Herring May 02 '19 at 13:06
  • @DavisHerring I think that the constructor part is obvious, since one is an object and the other is a pack of arguments, but sure feel free to add it as an answer. – lightxbulb May 02 '19 at 13:12

1 Answers1

2

Because the length of an initializer_list is not known at compile time (in particular, different call sites with different numbers of elements invoke the same function), it’s impossible to do any constexpr processing on it. (The relevant member functions are constexpr, but you can’t use them as such on a function parameter.) A fold expression in particular requires a parameter pack, whose size is always a constant expression.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76