Home > Back-end >  Parameter pack constructor preferred over other constructor calls
Parameter pack constructor preferred over other constructor calls

Time:07-27

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

CodePudding user response:

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);
}
  • Related