Why does this substitution failure create an error, again? [duplicate] - c++

This question already has answers here:
Why does this substitution failure create an error?
(2 answers)
Closed 1 year ago.
I asked a question just before about why std::enable_if<false> cannot be used in SFINAE contexts, as in:
template <typename T, typename DEFAULTVOID = void>
struct TemplatedStruct {};
template <typename T>
struct TemplatedStruct<T, std::enable_if_t<false>> {}; // enable_if expression
// isn't dependent on template type, is always false and so is an error
However in the following example it is dependent on a template argument, but this also creates an error:
#include <type_traits>
template <typename value_t_arg>
struct underlyingtype
{
static inline constexpr bool bIsIntegralType =
std::is_integral_v<value_t_arg>;
template <typename T, typename DEFAULTVOID = void>
struct IsSpecialType {
static inline constexpr bool bIsSpecialType = false;
};
template <typename T>
struct IsSpecialType<T, std::enable_if_t<bIsIntegralType>> {
static inline constexpr bool bIsSpecialType = true;
};
// This also creates an error, this is essentially the same as above
template <typename T>
struct IsSpecialType<T, std::enable_if_t<std::is_integral_v<value_t_arg>>> {
static inline constexpr bool bIsSpecialType = true;
};
};
int main()
{
underlyingtype<int> g1; // Works
underlyingtype<double> g2; // std::enable_if_t<false, void>:
// Failed to specialize alias template
}
In the first case of using std::enable_if_t<false> it fails to compile no matter what I instantiate. However in this other case underlyingtype<int> g1; works while when I instantiate it with a double it then fails to compile, which makes me think they're two different problems.
Edit: I should mention, this fails to compile with Visual Studio Community 2019 16.9.3.

// Failed to specialize alias template
For one, there's no alias template in your code.¹ You're just delcaring bIsIntegralType to be exactly the same thing as std::is_integral_v<value_t_arg>, which is fixed (to false or true) as soon as the instantiation of underlyingtype takes place.
Therefore, the two specializations
template <typename T>
struct IsSpecialType<T, std::enable_if_t<bIsIntegralType>> {
static inline constexpr bool bIsSpecialType = true;
};
// This also creates an error, this is essentially the same as above
template <typename T>
struct IsSpecialType<T, std::enable_if_t<std::is_integral_v<value_t_arg>>> {
static inline constexpr bool bIsSpecialType = true;
};
are the same thing, hence clang says
Class template partial specialization 'IsSpecialType<T>' cannot be redeclared
And this is independent of what value_t_arg you pass to underlyingtype.
When removing either of the two identical specializations, the code is ok as regards underlyingtype<int> g1;, but it is still invalid upon trying to instantiate underlyingtype<double>, because value_t_arg is "blocked" to double in that case, which makes bIsIntegralType be just a false compile-time value, which in turns means that you're passing an always-and-ever-false to std::enable_if_v.
Putting it in another way, when you ask for underlyingtype<double>, the compiler starts instantiating the class underlyingtype with value_t_arg = double; at this point the compiler hasn't even looked at IsSpecialType, but it knows that bIsIntegralType == false, which makes the code for IsSpecialType's specialization invalid as per the previous question.
(¹) An alias template is a templated type alias,
template <typename T>
using new_name = old_name<T>;
whereas in your code there's no using at all, so there couldn't be a type alias, let alone an alias template.
Based on this and the previous question, it looks like you're trying to get into SFINAE and Template Meta-Programming. If I may give you a suggestion, a good way to learn it is to read and understand how the Boost.Hana library works. There's a lot of TMP and SFINAE there, but the quality of the code is high (imho) and the code itself is extremely well documented and, hence, understandable (obviously it takes time).

Consider this line:
std::cout << underlyingtype<double>::IsSpecialType<char>::bIsSpecialType << "\n";
How should we go about interpreting it?
underlyingtype is a template.
underlyingtype<double> is not a template, it is a type, a specific instantiation of underlyingtype.
underlyingtype<double>::IsSpecialType is a template, a member of a (non-template) class type underlyingtype<double> This template has a single parameter T.
underlyingtype<double>::IsSpecialType<char> is an instantiation of the preceding template.
Now, when instantiating a template, its parameters are substituted with actual arguments. Failure to perform such substitution is not an error. In case of underlyingtype<double>::IsSpecialType, the parameter is T. However std::enable_if_t<std::is_integral_v<value_t_arg>>> does not depend on T, so no substitution takes place.

Related

When (if ever) C++ concepts must be “decayed”, do it at concept definition or at “requires”?

Imagine I have a concept that checks for some trait of a member type, e.g. its integralness. In anticipation of what is to come, let me define it in two ways:
namespace decayedAtConcept {
template <typename T>
concept hasIntegralMemberType = std::integral<typename std::decay_t<T>::MemberType>;
}
namespace notDecayedAtConcept {
template <typename T>
concept hasIntegralMemberType = std::integral<typename T::MemberType>;
}
With the following definitions
struct HasIntegralMemberType {using MemberType = int;};
struct HasNonintegralMemberType {using MemberType = double;};
struct DoesNotHaveMemberType {};
and the following function declarations
void usesIntegralMemberType1 (decayedAtConcept::hasIntegralMemberType auto&&);
void usesIntegralMemberType2 (notDecayedAtConcept::hasIntegralMemberType auto&&);
template <typename T> requires notDecayedAtConcept::hasIntegralMemberType<std::decay_t<T>>
void usesIntegralMemberType3 (T&&);
in effect, a client can try to use this library in the following way:
void client () {
HasIntegralMemberType h;
usesIntegralMemberType1(HasIntegralMemberType{});
usesIntegralMemberType1(h); // OK
//usesIntegralMemberType1(HasNonintegralMemberType{}); //note: constraints not satisfied
//usesIntegralMemberType1(DoesNotHaveMemberType{}); //note: constraints not satisfied
usesIntegralMemberType2(HasIntegralMemberType{});
//usesIntegralMemberType2(h); //error: ‘HasIntegralMemberType&’ is not a class, struct, or union type
usesIntegralMemberType3(HasIntegralMemberType{});
usesIntegralMemberType3(h); // OK again
}
The notes just demonstrate that the concept works as expected in usesIntegralMemberType1. My question is related to the error caused by the naive usage of the notDecayedAtConcept in usesIntegralMemberType2.
Apparently, the notDecayedAtConcept version of the concept is less convenient to use because any time it is used with a universal reference, it needs to be decayed in a requires clause as in usesIntegralMemberType3. We even lose the syntactic sugar we had in defining the constrained template parameter in usesIntegralMemberType1.
And now the question: are there already some guidelines which of the two strategies to use in such a situation? Or, is there any other, more transparent/convenient solution?

Get number of template parameters with template template function

I'm not sure if this is possible, but I would like to count the number of template arguments of any class like:
template <typename T>
class MyTemplateClass { ... };
template <typename T, typename U>
class MyTemplateClass2 { ... };
such that template_size<MyTemplateClass>() == 1 and template_size<MyTemplateClass2>() == 2. I'm a beginner to template templates, so I came up with this function which of course does not work:
template <template <typename... Ts> class T>
constexpr size_t template_size() {
return sizeof...(Ts);
}
because Ts can not be referenced. I also know that it might come to problems when handling variantic templates, but that is not the case, at least for my application.
Thx in advance
There is one...
° Introduction
Like #Yakk pointed out in his comment to my other answer (without saying it explicitly), it is not possible to 'count' the number of parameters declared by a template. It is, on the other hand, possible to 'count' the number of arguments passed to an instantiated template.
Like my other answer shows it, it is rather easy to count these arguments.
So...
If one cannot count parameters...
How would it be possible to instantiate a template without knowing the number of arguments this template is suppose to receive ???
Note
If you wonder why the word instantiate(d) has been stricken throughout this post,
you'll find its explanation in the footnote. So keep reading... ;)
° Searching Process
If one can manage somehow to try to instantiate a template with an increasing number of arguments, and then, detect when it fails using SFINAE (Substitution Failure Is Not An Error), it should be possible to find a generic solution to this problem... Don't you think ?
Obviously, if one wants to be able to also manage non-type parameters, it's dead.
But for templates having only typename parameters...
There is one...
Here are the elements with which one should be able to make it possible:
A template class declared with only typename parameters can receive any type as argument. Indeed, although there can have specializations defined for specific types,
a primary template cannot enforce the type of its arguments.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The above statement might no longer be true from C++20 concepts.
I cannot try ATM, but #Yakk seems rather confident on the subject. After his comment:
I think concepts breaks this. "a primary template cannot enforce the type of its arguments." is false.
He might be right if constraints are apply before the template instantiation. But...
By doing a quick jump to the introduction to Constraints and concepts, one can read, after the first code example:
"Violations of constraints are detected at compile time, early in the template instantiation process, which leads to easy to follow error messages."
To be confirmed...
It is perfectly possible to create a template having for sole purpose to
be instantiated with any number of arguments. For our use case here, it might contain only ints... (let's call it IPack).
It is possible to define a member template of IPack to define the Next IPack by adding an int to the arguments of the current IPack. So that one can progressively increase its number of arguments...
Here is maybe the missing piece. It is maybe something that most people don't realize.
(I mean, most of us uses it a lot with templates when, for example, the template accesses a member that one of its arguments must have, or when a trait tests for the existence of a specific overload, etc...)
But I think it might help in finding solutions sometimes to view it differently and say:
It is possible to declare an arbitrary type, built by assembling other types, for which the evaluation by the compiler can be delayed until it is effectively used.
Thus, it will be possible to inject the arguments of an IPack into another template...
Lastly, one should be able to detect if the operation succeeded with a testing trait making use of decltype and std::declval. (note: In the end, none of both have been used)
° Building Blocks
Step 1: IPack
template<typename...Ts>
struct IPack {
private:
template<typename U> struct Add1 {};
template<typename...Us> struct Add1<IPack<Us...>> { using Type = IPack<Us..., int>; };
public:
using Next = typename Add1<IPack<Ts...>>::Type;
static constexpr std::size_t Size = sizeof...(Ts);
};
using IPack0 = IPack<>;
using IPack1 = typename IPack0::Next;
using IPack2 = typename IPack1::Next;
using IPack3 = typename IPack2::Next;
constexpr std::size_t tp3Size = IPack3::Size; // 3
Now, one has a means to increase the number of arguments,
with a convenient way to retrieve the size of the IPack.
Next, one needs something to build an arbitrary type
by injecting the arguments of the IPack into another template.
Step 2: IPackInjector
An example on how the arguments of a template can be injected into another template.
It uses a template specialization to extract the arguments of an IPack,
and then, inject them into the Target.
template<typename P, template <typename...> class Target>
struct IPackInjector { using Type = void; };
template<typename...Ts, template <typename...> class Target>
struct IPackInjector<IPack<Ts...>, Target> { using Type = Target<Ts...>; };
template<typename T, typename U>
struct Victim;
template<typename P, template <typename...> class Target>
using IPInj = IPackInjector<P, Target>;
//using V1 = typename IPInj<IPack1, Victim>::Type; // error: "Too few arguments"
using V2 = typename IPInj<IPack2, Victim>::Type; // Victim<int, int>
//using V3 = typename IPInj<IPack3, Victim>::Type; // error: "Too many arguments"
Now, one has a means to inject the arguments of an IPack
into a Victim template, but, as one can see, evaluating Type
directly generates an error if the number of arguments does not
match the declaration of the Victim template...
Note
Have you noticed that the Victim template is not fully defined ?
It is not a complete type. It's only a forward declaration of a template.
The template to be tested will not need to be a complete type
for this solution to work as expected... ;)
If one wants to be able to pass this arbitrary built type to some detection trait one will have to find a way to delay its evaluation.
It turns out that the 'trick' (if one could say) is rather simple.
It is related to dependant names. You know this annoying rule
that enforces you to add ::template everytime you access a member template
of a template... In fact, this rule also enforces the compiler not to
evaluate an expression containing dependant names until it is
effectively used...
Oh I see ! ...
So, one only needs to prepare the IPackInjectors without
accessing its Type member, and then, pass it to our test trait, right ?
It could be done using something like that:
using TPI1 = IPackInjector<IPack1, Victim>; // No error
using TPI2 = IPackInjector<IPack2, Victim>; // No error
using TPI3 = IPackInjector<IPack3, Victim>; // No error
Indeed, the above example does not generate errors, and it confirms
that there is a means to prepare the types to be built and evaluate
them at later time.
Unfortunately, it won't be possible to pass these pre-configured
type builders to our test trait because one wants to use SFINAE
to detect if the arbitrary type can be instantiated or not.
And this is, once again, related to dependent name...
The SFINAE rule can be exploited to make the compiler silently
select another template (or overload) only if the substitution
of a parameter in a template is a dependant name.
In clear: Only for a parameter of the current template instantiation.
Hence, for the detection to work properly without generating
errors, the arbitrary type used for the test will have to be
built within the test trait with, at least, one of its parameters.
The result of the test will be assigned to the Success member...
Step 3: TypeTestor
template<typename T, template <typename...> class C>
struct TypeTestor {};
template<typename...Ts, template <typename...> class C>
struct TypeTestor<IPack<Ts...>, C>
{
private:
template<template <typename...> class D, typename V = D<Ts...>>
static constexpr bool Test(int) { return true; }
template<template <typename...> class D>
static constexpr bool Test(...) { return false; }
public:
static constexpr bool Success = Test<C>(42);
};
Now, and finally, one needs a machinery that will successively try
to instantiate our Victim template with an increasing number of arguments. There are a few things to pay attention to:
A template cannot be declared with no parameters, but it can:
Have only a parameter pack, or,
Have all its parameters defaulted.
If the test procedure begins by a failure, it means that the template must take more arguments. So, the testing must continue until a success, and then, continue until the first failure.
I first thought that it might make the iteration algorithm using template specializations a bit complicated... But after having thought a little about it, it turns out that the start conditions are not relevant.
One only needs to detect when the last test was true and next test will be false.
There must have a limit to the number of tests.
Indeed, a template can have a parameter pack, and such a template can receive an undetermined number of arguments...
Step 4: TemplateArity
template<template <typename...> class C, std::size_t Limit = 32>
struct TemplateArity
{
private:
template<typename P> using TST = TypeTestor<P, C>;
template<std::size_t I, typename P, bool Last, bool Next>
struct CheckNext {
using PN = typename P::Next;
static constexpr std::size_t Count = CheckNext<I - 1, PN, TST<P>::Success, TST<PN>::Success>::Count;
};
template<typename P, bool Last, bool Next>
struct CheckNext<0, P, Last, Next> { static constexpr std::size_t Count = Limit; };
template<std::size_t I, typename P>
struct CheckNext<I, P, true, false> { static constexpr std::size_t Count = (P::Size - 1); };
public:
static constexpr std::size_t Max = Limit;
static constexpr std::size_t Value = CheckNext<Max, IPack<>, false, false>::Count;
};
template<typename T = int, typename U = short, typename V = long>
struct Defaulted;
template<typename T, typename...Ts>
struct ParamPack;
constexpr std::size_t size1 = TemplateArity<Victim>::Value; // 2
constexpr std::size_t size2 = TemplateArity<Defaulted>::Value; // 3
constexpr std::size_t size3 = TemplateArity<ParamPack>::Value; // 32 -> TemplateArity<ParamPack>::Max;
° Conclusion
In the end, the algorithm to solve the problem is not that much complicated...
After having found the 'tools' with which it would be possible to do it, it only was a matter, as very often, of putting the right pieces at the right places... :P
Enjoy !
° Important Footnote
Here is the reason why the word intantiate(d) has been stricken at the places where it was used in relation to the Victim template.
The word instantiate(d) is simply not the right word...
It would have been better to use try to declare, or to alias the type of a future instantiation of the Victim template.
(which would have been extremely boring) :P
Indeed, none of the Victim templates gets ever instantiated within the code of this solution...
As a proof, it should be enough to see that all tests, made in the code above, are made only on forward declarations of templates.
And if you're still in doubt...
using A = Victim<int>; // error: "Too few arguments"
using B = Victim<int, int>; // No errors
template struct Victim<int, int>;
// ^^^^^^^^^^^^^^^^
// Warning: "Explicit instantiation has no definition"
In the end, there's a full sentence of the introduction which might be stricken, because this solution seems to demonstrate that:
It is possible to 'count' the number of parameters declared by a template...
Without instantiation of this template.
#include <utility>
#include <iostream>
template<template<class...>class>
struct ztag_t {};
template <template<class>class T>
constexpr std::size_t template_size_helper(ztag_t<T>) {
return 1;
}
template <template<class, class>class T>
constexpr std::size_t template_size_helper(ztag_t<T>) {
return 2;
}
template <typename T>
class MyTemplateClass { };
template <typename T, typename U>
class MyTemplateClass2 { };
template<template<class...>class T>
struct template_size:
std::integral_constant<
std::size_t,
template_size_helper(ztag_t<T>{})
>
{};
int main() {
std::cout << template_size<MyTemplateClass>::value << "\n";
std::cout << template_size<MyTemplateClass2>::value << "\n";
}
I know of no way without writing out the N overloads to support up to N arguments.
Live example.
Reflection will, of course, make this trivial.
° Before Reading This Post
This post does not answer to "How to get the number of parameters",
it answers to "how to get the number of arguments"...
It is let here for two reasons:
It might help someone who would have mixed up (like I did)
the meaning of parameters and arguments.
The techniques used in this post are closely related to the ones used
to produce the correct answer I've posted as a separate answer.
See my other answer for finding "the number of parameters" of a template.
The answer of Elliott looks more like what one usually does (though the primary template should be fully defined and "do something" IMHO). It uses a template specialization for when a template is passed as argument to the primary template.
Meanwhile, the answer of Elliott vanished...
So I've posted something similar to what he showed below.
(see "Generic Solution")
But, just to show you that you weren't that far from a working solution, and, because I noticed that you have used a function for your try, and, you declared this function constexpr, you could have written it like that:
Note
It is a 'fancy solution', but it works...
template <typename T> class MyTemplateClass {};
template <typename T, typename U> class MyTemplateClass2 {};
template <template <typename...> class T, typename...Ts>
constexpr const size_t template_size(T<Ts...> && v)
{
return sizeof...(Ts);
}
// If the target templates are complete and default constructible.
constexpr size_t size1 = template_size(MyTemplateClass<int>{});
constexpr size_t size2 = template_size(MyTemplateClass2<int, short>{});
// If the target templates are complete but NOT default constructible.
constexpr size_t size3 = template_size(decltype(std::declval<MyTemplateClass<int>>()){});
constexpr size_t size4 = template_size(decltype(std::declval<MyTemplateClass2<int, short>>()){});
Explanation
You said "because Ts can not be referenced", which is true and false, because of the way you made the declaration of template_size.
That is, a template template parameter cannot declare parameters itself (where you placed Ts in the declaration of the function template). It is allowed to do so to give a clue of what the template argument is expected to receive as argument, but it is not a declaration of a parameter name for the current template declaration...
(I hope it's clear enough) ;)
Obviously, it might be a little bit over complicated, but it worth knowing I think that such a construct is possible also... ;)
Generic Solution
template <typename T> class MyTemplateClass {};
template <typename T, typename U> class MyTemplateClass2 {};
template<typename T>
struct NbParams { static constexpr std::size_t Value = 0; };
template<template <typename...> class C, typename...Ts>
struct NbParams<C<Ts...>> { static constexpr std::size_t Value = sizeof...(Ts); };
constexpr size_t size1 = NbParams<MyTemplateClass<int>>::Value;
constexpr size_t size2 = NbParams<MyTemplateClass2<int, short>>::Value;
That is the regular way one does this kind of things... ;)

Deduce type of reference template parameter

If one has a
template <class T>
class A{};
// global namespace, static storage duration
static constexpr A<int> a;
is it possible to deduce the type A<int> by passing a as a reference template param such as:
// This question asks to make the syntax in the following line compile:
static_assert(std::is_same<A<int>, typename GetReferenceType<a>::type>::value, "");
// I am aware the next line works, but it's not what I am looking for in this question
static_assert(std::is_same<A<int>, decltype(a)>::value, "");
// This is pseudo code of how this could work in theory
template <const T& RefT, class T> // I know this does not compile, but this shows what I want
struct GetReferenceType{ // which is automatically deduce type `T` without having to
using type = T; // write it out
};
An answer which explains why this is not possible in C++ is as welcome as a solution to make this syntax compile :)
I am mostly asking out of curiosity, since basically everything else can be deduced in templates, but apparently not reference types.
This should also work, but does not fulfill the above syntax requirement:
template <class T>
constexpr auto GetReferenceTypeFunc(const T& t) -> T;
static_assert(std::is_same<A<int>, decltype(GetReferenceTypeFunc(a))>::value, "");
Why I want to do this
I am striving for the most concise syntax.
While Instantiate<decltype(a)> works, it's not ranking highly in the terseness scale, especially if a syntax like Instantiate<a> was possible.
Imagine a does not have a short type A<int> but instead something like
A<OmgWhyIsThisTypeNameSoLong>.
Then, if you want to instantiate a type with A<OmgWhyIsThisTypeNameSoLong>, you'd have to write:
Instantiate<A<OmgWhyIsThisTypeNameSoLong>>;
It just so happens that we already have a global object a, so it would be nice not to have to write that long type but instead Instantiate<a>.
There is of course the option of creating an alias using AOmg = A<OmgWhyIsThisTypeNameSoLong> but I would really like to get around spamming the namespace with another very similar name to A.
In C++20, you might do:
template <auto V>
struct GetReferenceType
{
using type = std::decay_t<decltype(V)>;
};
static_assert(std::is_same<A<int>, GetReferenceType<a>::type>::value);
but decltype seems sufficient.
Demo
// I not only want to deduce A<int> but also int
So you probably want a trait like:
template <typename> struct t_parameter;
template <template <typename > class C, typename T> struct t_parameter<C<T>>
{
using type = T;
};
but simple alternative is to add info directly in A:
template <class T>
class A{
using value_type = T;
};

How to fix previously-working injected template friend function?

I have recently updated gcc compiler from version 5 to 8, and it has broken our production code. A simplified version of the broken code is included below:
#include <utility>
// Imagine this has several template parameters not just Id and
// this class provides lots of friend functions for retrieving
// all this "metadata". Just one is implemented in this example.
template <typename Tag, unsigned Id>
class MetadataImpl
{
template <typename T, typename U>
using matches =
typename std::enable_if<std::is_same<T, U>::value>::type;
template <typename _Tag, typename = matches<_Tag, Tag>>
friend unsigned GetId(Tag* = nullptr)
{ return Id; }
};
// Let's generate some instances...
template class MetadataImpl<int, 1>;
template class MetadataImpl<double, 2>;
// And a simple test function.
unsigned test()
{
return GetId<int>();
}
In simplest terms, this code provides a way of capturing metadata around a tag (a type in the example above, but could also be an enum value) and was originally coded some 10+ years ago and has seen many gcc upgrades, but something "broke" in gcc 6 (verified via the famous godbolt online compiler).
It is quite possible that this code wasn't supported by the c++ standard and was just a gcc extension which has now been dropped, but I would be interested to know if this was actually the case and what the rationale might be for it being rejected by the standard.
It seems also that clang doesn't support this code either but I have noticed that if you do an ast-dump (clang -Xclang -ast-dump) that clang does at least hold the definitions of these friend functions, but it seems it is unable to find them when used (a template argument deduction failure?).
I would be very delighted to know of any work-around or alternative that works in as similar a way as possible, i.e. though some form of single line instantiation and, critically, only for tags that have been explicitly instantiated.
Specifically, what I don't want is to have a string of template functions that all have to be implemented per tag (I've just shown one metadata item and there are many in the production code, some of which derive further information from combinations of template arguments and/or other type information). The original solution developed above led to very clean, extendable and maintainable code. Wrapping it all in some complex macro would be the absolute worst-case scenario!
There is a similar question and answer here but I can't see how to make this solution work in this scenario since the argument to the friend function is not the parent class itself, but a template argument of it.
Changing the GetId function to take MetadataImpl<...> as its argument would not be a viable solution, since then the use of the functions becomes utterly impractical. The places where the functions are called from just want to provide the tag itself.
Thank you, in advance, for any help!
The reason it worked before is because gcc has bugs. It wasn't standard C++ and most probably won't ever be. But this is
namespace
{
template<typename T>
struct flag
{
friend constexpr unsigned adl(flag<T>);
};
template <typename T, unsigned n>
class meta
{
friend constexpr unsigned adl(flag<T>)
{
return n;
}
};
template<typename T>
constexpr auto getId()
{
return adl(flag<T>{});
}
}
And you get to write the exact same thing as before
template class meta<int, 1>;
template class meta<double, 2>;
auto foo()
{
return getId<int>();
}
Note the anonymous namespace, you run afoul the ODR if you don't have it.
Why don't you just write GetId as a free function and specialize it as needed?
template <typename Tag>
unsigned GetId()
{
return /* default value */;
}
template <> unsigned GetId<int> () { return 1; }
template <> unsigned GetId<double>() { return 2; }
// ...
A regex replace can help you with transforming the class template explicit instantiations to these function template specializations. (This is one of the few circumstances under which specializing a function template would make sense.)
If you don't want a default value, just define the primary function as = delete: (C++11)
template <typename Tag>
unsigned GetId() = delete;
If you can use variable templates, (C++14) you can make the code look prettier:
template <typename Tag>
unsigned Id = /* default value */;
template <> unsigned Id<int> = 1;
template <> unsigned Id<double> = 2;
// ...
So maybe this violates your "no strings of templates" requirement but you can use a tag helper struct:
template <typename T> struct tag {};
template <> struct tag<int> {
static constexpr unsigned Id = 1;
// any more customization points here
};
template <> struct tag<double> {
static constexpr unsigned Id = 2;
};
(That would also avoid the many explicit instantiations). The metadata implementation would be:
template <typename Tag>
class MetadataImpl
{
friend unsigned GetId(MetadataImpl)
{ return Tag::Id; }
};
and now you can write a helper to ADL call GetId.
template <typename T>
unsigned GetId() {
return GetId(MetadataImpl<tag<T>>());
}
Demo.

Why do `SFINAE` (std::enable_if) uses bool literals instead of `true_t` / `false_t` tag classes?

I am trying to learn about SFINAE (i am following this tutorial), but there are some... "design choices" I do not understand and, as such, I find them confusing.
Let's assume I have a situation like this (included re-implementation of std::enable_if is there just to demonstrate how I understand enable_if)
// A default class (class type) declaration. Nothing unusual.
template <bool, typename T = void>
struct enable_if
{};
// A specialisation for <true, T> case. I understand 'why-s' of this.
// -- 'why-s': if I attempt to access 'enable_if<false, T>::type' (which does not exist) I will get a substitution failure and compiler will just "move-on" trying to match "other cases".
template <typename T>
struct enable_if<true, T> {
typedef T type;
};
// Here lies my problem:
template <class T,
typename std::enable_if<std::is_integral<T>::value,T>::type* = nullptr>
void do_stuff(T& t) { /* do stuff */ };
(1) The very 1st thing I have a "problem" with, is bool literal (true/false). I understand they are correct and templates can accept compile-time constant values of primitive data types (plain-old-data types) but if I were tasked to design the enable_if "mechanisms" instead of using true/false I would create a tag classes true_t(or True) and false_t (or False) as follows:
class true_t {}; // or True
class false_t {}; // or False
template<typename T>
class is_integral // just to have "something" to use with "enable_if"
{
using result = false_t;
};
template<>
class is_integral<int32_t> // same with all other int types
{
using result = true_t;
};
template <typename B, typename T = void>
struct enable_if
{};
template <typename T>
struct enable_if<true_t, T>
{
using type = T;
};
(2) The second thing I find redundant is the need to specify typename T template parameter. Wouldn't it be easier / better to just implement enable_if as follows:
template <typename B>
struct enable_if
{};
template <>
struct enable_if<true_t>
{
using type = void; // the 'type' exists therefore substitution failure will not occur.
};
I am well aware that all my propositions are extremely inferior to the currently existing solutions, but I don't understand why... What portion of the functionality (important functionality) of current SFINAE did i shave off? (Not even realizing it...)
I know that, on this site, I am obligated to ask a single question within a... single "question-post-like" format, but if you find it acceptable could I also ask what will this syntax:
std::enable_if</* ... */>::type* = nullptr
accomplish? It's beyond my understanding right now...
The very 1st thing I have a "problem" with, is bool literal (true/false). I understand they are correct and templates can accept compile-time constant values of primitive data types (plain-old-data types) but if I were tasked to design the enable_if "mechanisms" instead of using true/false I would create a tag classes true_t(or True) and false_t (or False) as follows
The issue with use a tag type instead of just a bool is that you have to add extra complexity to the code. If you want to check a compile time condition, like sizeof for instance, you couldn't just do sizeof(T) == 8. You would have to make an abstraction that does the check and the returns the appropriate tag type.
The second thing I find redundant is the need to specify typename T template parameter. Wouldn't it be easier / better to just implement enable_if as follows
Not really. What if you want to use the SFINAE for the return type? You would only be able to have a void function then, which is unnecessarily limiting. Instead what you can do is use what was later added in C++14 and C++17 and make aliases. This makes the names non dependent and lets you drop the typename
template< bool B, class T = void >
using enable_if_t = typename enable_if<B,T>::type;
template< class T >
inline constexpr bool is_integral_v = is_integral<T>::value;
This allows you to rewrite
template <class T,
typename std::enable_if<std::is_integral<T>::value,T>::type* = nullptr>
void do_stuff(T& t) { /* do stuff */ };
to
template <class T,
std::enable_if_t<std::is_integral_v<T>,T>* = nullptr>
void do_stuff(T& t) { /* do stuff */ };
although I prefer to use a bool for the type of enable_if_t like
template <class T,
std::enable_if_t<std::is_integral_v<T>, bool> = true>
void do_stuff(T& t) { /* do stuff */ };
I know that, on this site, I am obligated to ask a single question within a... single "question-post-like" format, but if you find it acceptable could I also ask what will this syntax:
std::enable_if</* ... */>::type* = nullptr
accomplish?
It makes a pointer to the type that std::enable_if "returns" and sets it to null pointer. The goal here is to make a template parameter that will only exist if the condition is true. You could rewrite it to
typename = typename std::enable_if</* ... */>::type
so instead of having a non type parameter you have a type parameter. They both accomplish the same thing but the latter wont work with overloading the function for different enable_if's since default template parameters are not part of the signature. The first version which uses non type parameters is included in the function signature and does allow you to overload the enable_if's.
First off there exists tag-types for true and false, namely std::true_type and std::false_type.
Let's say we made the enable_if work with this instead of a bool parameter. You could then no longer do things like std::enable_if<1 == 1>::type since 1 == 1 evaluates to a bool. So does most things you will want to test here.
On the other hand, the existing tag types can be used in a enable_if since they contain a value and have a operator() that return said value.
So it seems to me that a lot of convenience would be lost in doing it your way, and from what I can see nothing would be gained.
For point 2, it's simply a convenience to be able to specify what type you want enable_if to hold if it's true. It defaults to void, but if you want you can easily have it deduce an int, double ect. which can be useful sometimes.