Resolve Template Parameter Pack Overload Ambiguity

79 Views Asked by At

Minimum Example

I have the following struct with a few specializations:

template<size_t ...Tpar>
struct Base{};

struct X{};
struct Y{};

template<typename T, size_t ...Tpar>
struct Spline: Base<Tpar...>{}; // general type

template<typename T>
struct Spline<T>: Base<>{}; // specialization 1

template<typename T, size_t n>
struct Spline<T,n>: Base<n>{}; // specialization 2

template<size_t ...Tpar>
struct Spline<X,Tpar...>: Base<Tpar...>{}; // specialization 3

template<size_t ...Tpar>
struct Spline<Y,Tpar...>: Base<Tpar...>{}; // specialization 4

The implementation bodies are lengthy each, and have been spared for a minimum example.

Error

The following ambiguity arises for X (and analogously for Y):

int main(){
  Spline<X,56> sx56; // error: compiler cannot decide between spec. 2 and spec. 3.
}

Demo

Why the Overloading

I have one customer class code that is supposed to yield a wide range of different bahaviours depending on i) mainly the type and ii) subsequently the existence and value of additional template arguments. I have tried different things and this appeared convenient for what I am trying to do -- until now, where I wish to re-introduce Tpar... into specializations 3 and 4.

What I expected

I expected that specialization 3 would be used, because X is a type restriction whereas n is only an amount specialization.

What I want

I would like to declare that specialization 3 is to be used in this case. Since the implementations of 3 and 4 are huge, I would like to perform this declaration somewhere concisely.

Workaround

If a timely fix was mandatory, I would use the following:

template<typename T,size_t ...Tpar>
using Spline = std::conditional_t< std::is_value_t<T,X> , Spline_Spec3<Tpar...> , /*...*/ >;

But that would be

  1. error-prone
  2. difficult to read
  3. require nested std::conditional_t for X and Y
  4. require renaming of specialization class names
1

There are 1 best solutions below

1
On

As proposed by @HolyBlackCat.

#include<iostream>

template<size_t ...Tpar>
struct Base{};

struct X{};
struct Y{};

template<typename T>
concept is_default_floating_point_type = requires(T t){ 
    requires not( std::is_same_v<T,X> or std::is_same_v<T,Y> );
};

template<typename T, size_t ...Tpar>
struct Spline: Base<Tpar...>{}; // general type

template<typename T>
struct Spline<T>: Base<>{}; // specialization 1

template<typename T, size_t n>
    requires is_default_floating_point_type<T>
struct Spline<T,n>: Base<n>{}; // specialization 2

template<size_t ...Tpar>
struct Spline<X,Tpar...>: Base<Tpar...>{
  Spline(){ std::cout << "hi.\n"; }
}; // specialization 3

template<size_t ...Tpar>
struct Spline<Y,Tpar...>: Base<Tpar...>{}; // specialization 4

int main(){
  Spline<X,56> sx56; // now compiler selects specialization 3
}