Search code examples
c++templatespreprocessorparameter-pack

Parameter pack constructor preferred over other constructor calls


Consider a constructor accepting a parameter pack, such as

template<typename First, typename... Rest> 
consteval explicit foo(const First& first, const Rest... rest) 
    : arrayMember{first, rest...}
        {
        }

where First and Rest... all have arithmetic types,

And another, different constructor that takes two arithmetic types:

explicit foo(std::size_t low, std::size_t high) { /* ... */ }

Imagine now that we wish to call the second constructor taking the two arithmetic types parameters:

foo f(3,4);

... But this actually calls the constructor that takes a parameter pack.

I am wondering whether there is a way to disambiguate the calls. For example, is there a way (through preprocessor magics or something) to call the parameter pack constructor with brace initialization ({ ... }), and to call the other constructor with direct initialization ((..., ...))? I have thought of disabling the parameter pack constructor if the passed arguments are 2, but what if I actually wanted to call it with two parameters?

Minimal reproducible example: https://godbolt.org/z/5P9cEdf7v


Solution

  • Assuming an implementation similar to this:

    template <typename T, std::size_t Size>
    class foo {
       private:
        T _vector[Size]{};
    
       public:
        using size_type = std::size_t;
    
        // .. other constructors ..
    
        explicit foo(size_type lower, size_type higher) { // (1)
            // ...
        }
    
        template <typename Stream>
        constexpr void print(Stream& stream) const {
            for (auto x : _vector) stream << x << ' ';
            stream << '\n';
        }
    };
    

    You can replace your first constructor by one that takes a const reference to an array, T const (&)[Size]:

    consteval explicit foo(T const (&in)[Size]) // (2)
        : foo(in, std::make_index_sequence<Size>{}) {}
    

    This constructor delegates the initialization of _vector to another constructor which exploits the inices trick:

    template <std::size_t... Is>
    consteval explicit foo(T const (&in)[Size], std::index_sequence<Is...>) // (3)
        : _vector{in[Is]...} {}
    

    With this implementation:

    #include <iostream>
    int main() {
        foo<int, 3> vec0(3, 4); // calls (1)
        vec0.print(std::cout);
    
        constexpr foo<int, 3> vec1({3, 4}); // calls (2), which calls (3)
        vec1.print(std::cout);
    
        constexpr foo<int, 3> vec2{{3, 4}}; // calls (2), which calls (3)
        vec2.print(std::cout);
    }