1

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");
}

2 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

12 Comments

I knew I should have mentioned the obvious solution... That doesn't scale very well.
what do you mean by "it doesn't scale very well"?
For every variable, you have to repeat the class name. This makes it less concise than I'd like it to be.
@bgp2000 Do the constructor have to be explicit?
I'm not convinced that you need the argument checking here. Anything passing non-symbols fails to instantiate the template. template <class... Args> std::array<Symbol, sizeof...(Args)> make_symbols(Args... args) { return {Symbol{args}...}; }
|
1

The best I've come up with so far is:

std::vector<Symbol> symbols;
for(auto v: { "a", "b", "c"})
    symbols.emplace_back(v);

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.