StoryTeller StoryTeller - 3 months ago 19
C++ Question

SFINAE to make base template always result in error

So I'm designing a sort of

my_numeric_cast
function to limit the types of conversions available when using a framework I'm writing.

It was pretty straight forward to do something like

template<typename To, typename From>
constexpr To my_numeric_cast(From);

template<>
constexpr float my_numeric_cast<float, int>(int i) { return i; }


Which works, allowing only casting from ints to floats whenever the cast is used. And producing a linkage error whenever a cast not in the white list is attempted.

However, I'd really want to make this a compilation error, to catch the misuse much faster.

How do I make the base template body valid, expect when instantiating it?

Answer

You cannot write a template function specialization for which no template argument makes the body valid in C++. The result if you do so is an ill formed program with no diagnostic required. This includes the primary specialization.

So most of the answers here are simply undefined behaviour. They may work, but they are not valid C++. They may work today, but after a library upgrade a compiler upgrade or a different build target they could fail in completely different and surprising ways. Relying on UB with no strong reason is a bad idea.

On the plus side, we can do away with template specialization and fix your problem in one fell swoop:

template<class T>struct tag_t{}; // may need `constexpr tag_t(){}` on some compilers
template<class T>constexpr tag_t<T> tag{};

template<class T, class F>
constexpr T my_numeric_cast(F, tag_t<F>)=delete; // generates compile time error

constexpr float my_numeric_cast(int i, tag_t<float>) { return i; } // not a template!  Could be if you want it to be.

template<typename To, typename From>
constexpr To my_numeric_cast(From f){
  return my_numeric_cast(f, tag<To>);
}

and done.

=delete generates friendly messages. Program is well formed. Implementing casts is no longer a specialization. You can even implement it in the namespace of a type being cast to or from as ADL is enabled.

If you solve a problem with template function specialization, reconsider. They are fragile, do not work like class template specialization or function overloading (while looking like both of them!), and usually are not the best solution to anything. There are exceptions when it may be a good idea, but they are quite rare, and given how rare they are avoiding the quirky feature may still be worth it.