3

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).

Andrey
  • 5,932
  • 3
  • 17
  • 35
  • 6
    There are several things in the standard library that provide support for language constructs. This is one of them. Others include things like `operator new` and `operator delete`, which require code to implement the memory manager; code to actually run the program (i.e., startup code); support for `dynamic_cast`; and support for exception handling. There's nothing out of the ordinary to need library facilities for language support. – Pete Becker May 11 '22 at 18:08
  • 1
    "*Is it possible to create a constructor without use of `std:initializer_list?`*" - not the way you want, no. See [list initialization](https://en.cppreference.com/w/cpp/language/list_initialization), and pay attention to the section on how the compiler uses overload resolution to find a matching constructor for a brace-init-list when no constructor takes a `std::initializer_list`. For instance, in your example, `S(T, T, T, T, T)` would work, but I'm sure that is not what you want. – Remy Lebeau May 11 '22 at 18:08
  • 2
    You can only do this if you have an aggregate type. If your type is not an aggregate, then the only way to use a `std::intializer_list` – NathanOliver May 11 '22 at 18:10
  • 2
    A similar thing happens with `` (for `typeid`) and `` (for `<=>`) – Artyer May 11 '22 at 18:39
  • @RemyLebeau thanks, I found out, that I can use `S s = {.v = {1, 2, 3, 4, 5}};`. But I think it is not useful in real life because of existing limitations (e.g. there should not be private members in the class) – Andrey May 11 '22 at 19:22
  • 1
    @Andrey just note that [designated initializers](https://en.cppreference.com/w/cpp/language/aggregate_initialization#Designated_initializers) only work in C++20 and later – Remy Lebeau May 11 '22 at 19:45
  • 1
    There could be obscure workarounds, but if the standard library is actually available to you, you should use it. Yes, it's weird that language features and the standard library are mixed this way, but it is what it is. – HolyBlackCat May 11 '22 at 20:14
  • `std::initializer_list` can only be used for copyable type `T` (see [here](https://stackoverflow.com/questions/7231351/initializer-list-constructing-a-vector-of-noncopyable-but-movable-objects)). This can be quite a limitation. Have you ever tried to use an initializer list of `std::unique_ptr`s? Thus I can fully understand the wish for an alternative. – Jakob Stark May 11 '22 at 21:02

1 Answers1

3

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) {
    //...
}
Jakob Stark
  • 3,346
  • 6
  • 22
  • 3
    This is a correct way to accept a variable length list-initialization. However, the constraint is overly restrictive (in both versions). It's enough that each initializer can be losslessly converted (i.e. not a narrowing conversion) to the destination type. That is, for a built-in array, `T a[] = { 5, 'A' };` works for both `int` and `char`. – Ben Voigt May 11 '22 at 21:26