c++c++17string-viewmember-initializationstdinitializerlist

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.


Solution

  • 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() );