Search code examples
c++constructorinitializer-listbraced-init-list

How to create constructor for braced-init-list in c++ without standard library?


I would like to be able to initialize my objects with a brace-init-list:

#include <initializer_list>
 
template <class T>
struct S {
    T v[5];
    S(std::initializer_list<T> l) {
      int ind = 0;
      for (auto &&i: l){ 
        v[ind] = i; 
        ind++;
        }
    }
};
 
int main()
{
    S<int> s = {1, 2, 3, 4, 5}; 
}

As I found out here: https://en.cppreference.com/w/cpp/utility/initializer_list, it is necessary to use the standard library for this.

But that is strange for me. I would suggest that such initialization is a part of the C++ syntax.

Is it possible to create a constructor without use of std:initializer_list?

Edit:

It could be useful for programming embedding devices where standard library is not available (e.g. Arduino AVR).


Solution

  • As a work around one could do something like

    #include <iostream>
    
    template <class T>
    struct S {
        T v[5];
    
        template<typename... Args>
        requires (std::same_as<T, Args> && ...)
        S(Args... args) {
            static_assert(sizeof...(args) <= 5);
            int ind = 0;
            ((v[ind++] = args), ...);
        }
    };
     
    int main()
    {
        S<int> s = {1, 2, 3, 4, 5}; 
    
        for ( auto e : s.v ) {
            std::cout << e << ' ';
        }
    }
    
    

    live on godbolt

    As RemyLebeau pointed out in the comments a constructor like S(T,T,T,T,T) is needed. With a variadic constructor template and C++17 fold expressions you can make the compiler provide a constructor of that form for each number of arguments.

    Note that the requires constraint (C++20) is important if you want other constructors in that class. It constraints the template the form S(T,T,T,...) and will not take part in overload resolution for arguments, that are not all of type T. It should also be possible to achieve that pre C++20 with std::enable_if*.

    Also remember that the compiler will instantiate a constructor with matching signature for each call with a different number of arguments. Thus this could result in code bloat if used often and with many different numbers of arguments in the braces.


    * like this for example:

    template<typename... Args,
        typename std::enable_if_t<(std::is_same_v<T, Args> && ...), bool> = true>
    S(Args... args) {
        //...
    }