c++c++11aggregate-initializationexplicit-constructor

Initializing array through explicit constructor


I'm writing a class that has an explicit constructor taking a const char* argument. For the intents and purposes of this question it looks like this:

struct Symbol
{
    Symbol()=default;
    explicit Symbol(const char*);
};

Now I want to write an example for documentation purposes that initializes an array (array/vector/list - I don't care about the exact type) and I need the example to be as clear and concise as possible. Ideally it would look like this:

Symbol symbols[] = { "a", "b", "c"};

That does not compile because of the explicit keyword and I am not prepared to make the constructor implicit.

How can I make this work, with the focus of making the example code as expressive as possible?

EDIT: I went for Bolov's solution with a little help from Caleth:

struct Symbol
{
    Symbol();
    explicit Symbol(const char*);

    template <class... Args> 
    static std::array<Symbol, sizeof...(Args)> Array(Args... args)
    {
        return {Symbol{args}...}; 
    } 
};

int main()
{
    auto symbols = Symbol::Array("a", "b", "c");
}

Solution

  • Well, your constructor is explicit so you need to use it as such:

    Symbol symbols[] = {Symbol{"a"}, Symbol{"b"}, Symbol{"c"}};
    

    Both gcc and clang both the copy/move constructor and since C++17 that is the required behavior so there is no performance overhead.


    If you really want to keep the constructor explicit and be able to create an array without explicitly stating it for every element then you can create a helper function:

    template <class... Args,
              class Enable = std::enable_if_t<(... && std::is_same_v<Args, const char*>)>>
    auto make_symbols(Args... args) -> std::array<Symbol, sizeof...(Args)>
    {
        return {Symbol{args}...};
    }
    

    and use it like this:

    auto symbols = make_symbols("a", "b", "c");
    

    Again the move/copies are entirely elided.

    The make_symbols function uses C++17 features for checking the arguments types. If you need the constraint for a previous standard version (including C++11) see this answer Restrict variadic template arguments. Or, depending on your needs, removing the check can also be a choice.