c++standardsapi-designinitializer-listc++26

Why do we keep a redundant ctor in std::copyable_function?


According to the C++ docs, std::copyable_function has two overloaded ctors as follows:

template<class T, class... CArgs>
explicit copyable_function(std::in_place_type_t<T>, 
    CArgs&&... args);

template<class T, class U, class... CArgs>
explicit copyable_function(std::in_place_type_t<T>, 
    std::initializer_list<U> il, CArgs&&... args);

Note that both ctors are required to construct its underlying callable object using direct-non-list-initialization. Consider the following code:

struct Functor {
    Functor(int, int) {
    }

    Functor(std::initializer_list<int>) {
    }

    void operator()() const {
    }
};

std::copyable_function(std::in_place_type<Functor>, 1, 2) will call Functor::Functor(int, int) rather than Functor::Functor(std::initializer_list<int>).

If we want to call Functor::Functor(std::initializer_list<int>), just use std::copyable_function(std::in_place_type<Functor>, std::initializer_list{1, 2}).

Both cases are intuitive and no ambiguity. So, my question is:

Why do we need the second ctor while the first one is enough?


Solution

  • {..} has no types and can only be deduced in few cases:

    CArgs&& is not one of them so cannot deduce {1, 2} as std::initializer_list<int>

    The second overload is needed.

    You can see yourself with simplified example:

    template <typename T>
    void foo(T&&) {}
    
    int main() {
        foo({1, 2}); // Error
    }
    

    Demo