2

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

  • Are `First` and `Rest...` of the same type? – paolo Jul 26 '22 at 20:26
  • @paolo yep, they are required to be through std::same_as. (I have left that part out for simplicity) – user15532034 Jul 26 '22 at 20:27
  • Can you show the full definition of `foo` (or at least the bare minimum to compile your example)? – paolo Jul 26 '22 at 20:28
  • Sure, I've provided a link to godbolt at the end of the thread. – user15532034 Jul 26 '22 at 20:33
  • Why don't you have the first constructor just take a `T const (&in)[N]` and initialize member variable `_vector` with `in` ? This way, the first constructor can only be called as `vector v({0,1,2})` or `v{{0,1,2}}`. – paolo Jul 26 '22 at 20:36
  • Note that the constructor is consteval and I’d like it to remain like that. Would I need compile time recursion to initialize _vector through in? – user15532034 Jul 26 '22 at 20:43
  • 2
    I would approach this like the standard does and use tag dispatch. Create a tag like or use `std::piecewise_construct` for the first parameter type and then you can disambiguate between the overloads. – NathanOliver Jul 26 '22 at 21:00

1 Answers1

3

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);
}
paolo
  • 2,345
  • 1
  • 3
  • 17
  • That's *exactly* what I was looking for: genius! (Read an article about index_sequence and things are starting to be clear now... Thanks!) – user15532034 Jul 26 '22 at 21:27