SFINAE to make base template always result in error
Asked Answered
H

2

5

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?

Haircloth answered 31/8, 2016 at 13:8 Comment(2)
Why use a template at all? You can add overloads and if they call an overload that does not exist it is a compiler error.Ursi
@NathanOliver, You can't overload on the return type of a function. It will result in ambiguity more often than not.Haircloth
B
9

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.

Beaverette answered 31/8, 2016 at 13:35 Comment(6)
That was illuminating. I did specialization since it was the textbook example when I learned. Thank you!Haircloth
+1 for I like the use of = delete. Just for I'm curious, is my answer wrong as you mentioned? I was quite sure that it was robust, maybe a bit convoluted and far worse than your, but it is not based on UB. Am I wrong?Premer
@Premer nope. Your template specialization is valid for at least one type. The rule exists, as far as I can tell, to permit (future iterations of the) C++ language (without breaking changes) or a compiler by itself to do additional checks to validate template code prior to template argument substitution and error out early. Anything that would make the code invalid for all arguments is fair game this way.Beaverette
@Yakk Interesting indeed. Thank you for giving more details each time, really appreciated.Premer
Could thou quote the Holy Standard on this?Credible
@template [temp.res/8] and then into a twisted set of standardese passages. Be careful with the term specialization and template, they don't mean exactly what they mean outside the standard iirc. I have tried to search for a SO Q&A on it, did not find it in 5 minutes (but did find that temp res reference)Beaverette
P
3

You can use traits to get a compile time error:

template<typename, typename> struct RetTraits;
// Enable it for int -> float
template<> struct RetTraits<int, float> { using type = float; };

template<typename To, typename From>
using RetType = typename RetTraits<To, From>::type;

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

int main() {
    my_numeric_cast<int, float>(42);
    // This won't compile
    // my_numeric_cast<int, int>(42);
}
Premer answered 31/8, 2016 at 13:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.