Rob Lauer
Rob Lauer

Reputation: 19

Initialize std::list<CustomType> from std::initializer_list<std::string_view> in ctor's member initializer list

I have a Game class that stores the m_players as a data member (std::list<Player>) and each player has multiple data members, one of them being their m_name (std::string). When instantiating a game I want the user to provide the players' names. I want them to be able to use string literals or strings, so I think using a std::string_view is the best practice here (right?). I also want the game to be playable with an arbitrary number of players, so I think a std::initializer_list for the game's ctor's parameters is the way to go (right?). Below you find the code (godbolt link) I thought would work, but which gives me an error for the line of code trying to initialize m_players.

#include <iostream>
#include <string>
#include <list>


struct Player
{
    Player (std::string_view name)
    : m_name{name}
    {}

private:
    std::string m_name;
};

class Game
{
public:
    Game(const std::initializer_list<std::string_view> players)
    // error: no matching function for call to 'std::__cxx11::list<Player>::list(<brace-enclosed initializer list>)'
    : m_players{players}
    {}

private:
    std::list<Player> m_players;
};

int main()
{
    Game game1{"Ann", "Bob"};

    std::string name1{"Ann"};
    std::string name2{"Bob"};
    Game game2{name1, name2};
}

Two things work:

  1. Instead of using the member initializer list I can write this loop: for (const auto& player : players) { m_players.emplace_back(player); }

  2. If I use a fixed number of string views as input parameters instead of a std::initializer_list that works as well.

I don't know what's happening in the member initializer list, so I don't understand why this doesn't work. I thought the compiler would basically do exactly what the loop in the ctor body would do but my guess is that this is some kind of implicit casting issue?

If I'm not mistaking it shouldn't make a difference performancewise whether I use the member initializer list or the body with a loop, but I'm still curious why this doesn't work.

Upvotes: 0

Views: 163

Answers (2)

Rob Lauer
Rob Lauer

Reputation: 19

After looking at cpp reference I think I finally understand the issue. The member initializer list just calls the corresponding ctor for each data member, which in my case is a std::list ctor. No matter what I choose as an input type (strings or string literals), the type of the ctor's input parameter (players) is const std::initializer_list<std::string_view> (T = std::string_view). m_players is of type std::list<Player> (T = Player) and if you check the documentation of the std::list container you'll find that it offers several ctors and one of them accepts a std::initializier_list<T> where T is the same type as the one for std::list<T>. In my case they are different though (std::string_view != Player) and that's the issue. One way to solve this is to use a different ctor as proposed by @super.

: m_players{players.begin(), players.end()}

will call this ctor:

template< class InputIt >
list( InputIt first, InputIt last,
      const Allocator& alloc = Allocator() );

Upvotes: 0

0xbachmann
0xbachmann

Reputation: 1090

std::list<T> be constructed from a std::initializer_list<T> but Game takes a std::initializer_list<std::string_view>. As far as C++ is concerned, two different template instantiations of a template class are completely separate types and have no relation to each other. This is why it is not possible to convert from std::initializer_list<std::string_view> to std::initializer_list<Player> even though Player is implicitly constructible from std::string_view.

Note that this works:

std::list<Player> players{"Anna", "Bob"};

while this doesn't:

std::initializer_list<std::string_view> names = {"Anna", "Bob"};
std::list<Player> players{names};

Because in the first example, the brace-enclosed initializer list is converted to std::initializer_list<Player>, required by std::list. But in the second example the brace-enclosed initializer list is converted to a std::initializer_list<std::string_view> and then there is no way to convert it back.

Following the recommendation from @super and writing m_players{players.begin(), players.end()} calls std::lists constructor which takes two iterators.

Upvotes: 0

Related Questions