Is there a macro to return (ideally as a const char* or indeed cont char *const) the typename of its parameter or some information on it?
This is at compile time, not runtime, so C++'s typeinfo will not do. I run with -fno-rtti anyway.
There isn't.
C++11 does have the __func__ identifier inside function definitions, which produces a string variable with the function name. Most C++ compilers have variations on this to have "decorated" function names.
Instead of writing
cout<<mysteryMacro(std::string<wchar>);
you could write
cout<<"std::string<wchar>";
The only purpose I see is to get typeinformation inside a template, e.g.:
template<T>
void printT() {
cout << "T is of type"<<mysteryMacro(T);
}
Since otherwise you automatically know the code.
This could be achieved with template specialization.
Disclaimer: this was untested, and is just a hint for what SHOULD work. I dont consider this a good solution. I am sure you can convert this to rather return strings than to print. A little more input on what you need this for would be great.
template<T>
void printT() {
code that generates compile error, so you see that you have an undefined type
}
template<>
void printT<string>() {
cout << "you have a <string>";
}
If you would like to use the template for variables you would hav to rely on automatic template parameter deduction like so:
template<>
void printT<string>(T) {
cout << "you have a <string>";
}
and use it like this
int x;
printT(x);
eventhough I do not know why, since, as stated unless you are in a template you will know the type while writing the code and in templates the type is conveyed in the template parameters and thus knwon (not as string) and you could always write printT<T>()
Related
I tried to play with the C++17 standard. I tried to use one of the features of C++17 if constexpr. And I had a problem... Please take a look at the following code. This compiles without errors. In the following code, I tried to use if constexpr to check if it is a pointer.
#include <iostream>
#include <type_traits>
template <typename T>
void print(T value)
{
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Ok
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
auto n = 1000;
print(n);
print(&n);
}
But when I rewrite the above code, as shown below, where if constexpr is in the main function:
#include <iostream>
#include <type_traits>
int main()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
I get a compilation error:
main.cpp:8:32: error: invalid type argument of unary ‘*’ (have ‘int’)
std::cout << "Ptr to " << *value << std::endl;
Problem is not in the main function. This can be any function similar to the following.
void print()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
print();
}
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
I would like to know why "if constexpr" works only in template functions, even if the type is deduced by the decltype from the input parameter.
This is by design.
if constexpr will not instantiate the branch not taken if it's within a template. It won't just treat the branch not taken as token soup and avoid parsing it or performing semantic analysis entirely. Both sides are still going to be analyzed, and since *value is ill-formed for ints, that's an error.
You simply can't use if constexpr to avoid compiling non-template code. It's only to avoid instantiating template code that's potentially invalid-for-the-particular-specialization.
C++ standard, clause 9.4.1:
If the if statement is of the form if constexpr, the value of the condition shall be a contextually converted constant expression of type bool (8.6); this form is called a constexpr if statement. If the value of the converted
condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity (Clause 17), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.
(emphasis mine)
So, a substatement of a constexpr if still gets instantiated if it is not inside a template, thus it must at least compile.
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
The thing is, it also work in non template, just not in the way you would expect.
For if constexpr to work like you stated, you not only need a template, but you need the contained expressions to be dependent on the template parameters.
Let's go step by step why this is made that way in C++, and what are the implications.
Let's start simple. Does the following code compile?
void func_a() {
nonexistant();
}
I think we will all agree that it won't compile, we are trying to use a function that hasn't been declared.
Let's add one layer.
Does the following code compile?
template<typename T>
void func_b_1() {
nonexistant();
}
With a correct compiler, this will not compile.
But why is that? You could argue that this code is never actually compiled, since the template is never instantiated.
The standard define something they call two phase name lookup. This is that even if the template is not instantiated, the compiler must perform name lookup an anything that don't depend on the template parameter.
And that make sense. If the expression nonexistant() don't depend on T, why would its meaning change with T? Hence, this expression is the same as in func_a in the eye of the compiler.
So how about dependent names?
template<typename T>
void func_b_2() {
T::nonexistant();
}
This will compile! Why is that? Nowhere in this code there's a function called nonexistant. Yet, you feed that into a compiler as the whole codebase and it will gladly accept it.
And the standard even says that it has to accept it. This is since there could be a T containing nonexistant somewhere. So if you instantiate the template with a type that has the static member function nonexistant it will compile and call the function. If you instantiate the template with a type that don't have the function, it won't compile.
As you can see, the name lookup is done during instantiation. This is called second phase name lookup. The second phase name lookup is done only during instantiation.
Now, enter if constexpr.
To make such construct working well with the rest of the language properly, it has been decided that if constexpr is defined as a branch for instantiation. As such, we can make some code non-instantiated, even in non templates!
extern int a;
void helper_1(int*);
void func_c() {
if constexpr (false) {
helper_1(&a);
}
}
The answer is that helper_1 and a are not ODR used. We could leave helper_1 and a not defined and there would not be linker errors.
Even better, the compiler won't instantiate templates that are in a discarded branch of a if constexpr:
template<typename T>
void helper_2() {
T::nonexistant();
}
void func_d() {
if constexpr (false) {
helper_2<int>();
}
}
This code won't compile with a normal if.
As you can see, the discarded branch of a if constexpr work just like a template that hasn't been instantiated, even in non template code.
Now let's mix it up:
template<typename T>
void func_b_3() {
if constexpr (false) {
nonexistant();
}
}
This is just like our template function in the beginning. We said that even if the template was not instantiated, the code was invalid, since the invalid expression don't depend on T. We also said that if constexpr is a branch in the instantiation process. The error happen before instantiation. This code won't compile either.
So finally, this code won't compile either:
void func_e() {
if constexpr (false) {
nonexistant();
}
}
Even though the content of the if constexpr is not instantiated, the error happen because the fist name lookup step is done, and the error happen before the instantiation process. It is just that in this case, there is no instantiation, but it doesn't matter at this point.
So what are the uses of if constexpr? Why does it seem to work only in templates?
The thing is, it doesn't work differently in templates. Just as we saw with func_b_3, the error still happen.
But, this case will work:
template<typename T>
void helper_3() {
if constexpr (false) {
T::nonexistant();
}
}
void func_f() {
helper_3<int>();
}
The expression int::nonexistant() is invalid, but the code compile. This is because since T::nonexistant() is an expression that depends on T, name lookup is done in the second phase. The second phase of name lookup is done during template instantiation. The if constexpr branch that contain T::nonexistant() is always the discarded part so the second phase of name lookup is never done.
There you go. if constexpr is not about not compiling a portion of code. Just like template, they are compiled and any expression that name lookup can be done is done. if constexpr is about controlling instantiation, even in non template function. All rules that applies to templates also applies to all branch of the if constexpr. Two phase name lookup still applies and allow programmers to not instantiate some part of the code that would otherwise not compile if instantiated.
So if a code cannot compiled in a template that is not instantiated, it won't compile in the branch of the if constexpr that is not instantiated.
Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive.
here
I am a little late to the party but a nice trick I use when I need if constexpr in a non template function is to wrap my code in a lambda.
struct Empty
{
};
void foo()
{
/* foo is not template, will not compile
if constexpr ( false )
{
std::cout << Empty{}.bar; // does not exist
}
*/
// now the code is in a lambda with auto param, so basicly a template method
[&](const auto& empty)
{
if constexpr ( false )
{
std::cout << empty.bar;
}
}(Empty{});
}
demo : https://wandbox.org/permlink/XEgZ6PXPLyyjXDlO
The fact I can use the [&] syntax make this solution way simpler to use than create an helper method since I don't need to forward all the parameters I need inside my if constexpr
I have seen that one of the always given reasons of using trailing return types is when we want to deduce the return type from the input arguments.
I know there are other reasons but I'm focusing this specific one in this question.
One of the given example is:
template <typename T>
auto func(const T & t) -> decltype(std::cout << t)
{
return std::cout << t;
}
But I could not figure out any concrete use-case of this.
I mean, we always know the return type of a function when we write it, I can't find any concrete example when deducing the return type from arguments is really needed and cannot be avoided.
We can always (if I'm not mistaken) rewrite the function's prototype by directly specifying the return type without any deduction which makes it more concise and clearer in my sense.
The above example can be rewritten as:
template <typename T>
std::ostream& func(const T & t)
{
return std::cout << t;
}
In my opinion, this is less verbose and more readable than the trailing return type version.
What am I missing ?
I mean, we always know the return type of a function when we write it
Do we? So If you write this function template:
template<typename A, typename B>
/* ret */ foo(A a, B b) {
return a + b;
}
You can say for sure what ret is? If given two integer then it's an integer, sure. But if provided an integer and a long, it should be long due to promotions. And if one argument is a double, than the result should be a double two.
And what if that's two objects of some class types? Now we are calling an overloaded operator+, and there's absolutely no guessing what it may return.
I hope your'e convinced now that by saying we accept any two types, we cannot always be sure what is the type of an expression involving those types.
So a mechanism was added to the language to tell. Granted, this example is overly simple and is likely superseded by auto return types, but the general principle remains. When writing generic code, we often deal with unknown types. There is almost no knowing what should be the type of an expression involving them, or even if an expression like that is valid before the function is instantiated. decltype tells us that.
The above example can be rewritten as:
No it cannot. Your conclusion is based on the assumption that there is a:
std::ostream& operator<<(std::ostream&,const T&);
But this need not be the case. It could be a
any_other_type operator<<(std::ostream&,const T&);
Then your version of the method would fail to compile, while the one with deduced return type would be fine.
For a concrete use case, consider some kind of io-manipulation that lets you write code such as
std::cout << all_caps_modifier << "some text" << back_to_normal_modifier << " more text";
That would print:
SOME TEXT more text
I admit this is a rather contrived example, but using some proxy that encapsulates std::cout and makes the string getting printed in capital letters is a possible way to implement it ( std::cout << all_caps_modifier would return a type different from std::ostream&).
I tried to play with the C++17 standard. I tried to use one of the features of C++17 if constexpr. And I had a problem... Please take a look at the following code. This compiles without errors. In the following code, I tried to use if constexpr to check if it is a pointer.
#include <iostream>
#include <type_traits>
template <typename T>
void print(T value)
{
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Ok
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
auto n = 1000;
print(n);
print(&n);
}
But when I rewrite the above code, as shown below, where if constexpr is in the main function:
#include <iostream>
#include <type_traits>
int main()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
I get a compilation error:
main.cpp:8:32: error: invalid type argument of unary ‘*’ (have ‘int’)
std::cout << "Ptr to " << *value << std::endl;
Problem is not in the main function. This can be any function similar to the following.
void print()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
print();
}
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
I would like to know why "if constexpr" works only in template functions, even if the type is deduced by the decltype from the input parameter.
This is by design.
if constexpr will not instantiate the branch not taken if it's within a template. It won't just treat the branch not taken as token soup and avoid parsing it or performing semantic analysis entirely. Both sides are still going to be analyzed, and since *value is ill-formed for ints, that's an error.
You simply can't use if constexpr to avoid compiling non-template code. It's only to avoid instantiating template code that's potentially invalid-for-the-particular-specialization.
C++ standard, clause 9.4.1:
If the if statement is of the form if constexpr, the value of the condition shall be a contextually converted constant expression of type bool (8.6); this form is called a constexpr if statement. If the value of the converted
condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity (Clause 17), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.
(emphasis mine)
So, a substatement of a constexpr if still gets instantiated if it is not inside a template, thus it must at least compile.
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
The thing is, it also work in non template, just not in the way you would expect.
For if constexpr to work like you stated, you not only need a template, but you need the contained expressions to be dependent on the template parameters.
Let's go step by step why this is made that way in C++, and what are the implications.
Let's start simple. Does the following code compile?
void func_a() {
nonexistant();
}
I think we will all agree that it won't compile, we are trying to use a function that hasn't been declared.
Let's add one layer.
Does the following code compile?
template<typename T>
void func_b_1() {
nonexistant();
}
With a correct compiler, this will not compile.
But why is that? You could argue that this code is never actually compiled, since the template is never instantiated.
The standard define something they call two phase name lookup. This is that even if the template is not instantiated, the compiler must perform name lookup an anything that don't depend on the template parameter.
And that make sense. If the expression nonexistant() don't depend on T, why would its meaning change with T? Hence, this expression is the same as in func_a in the eye of the compiler.
So how about dependent names?
template<typename T>
void func_b_2() {
T::nonexistant();
}
This will compile! Why is that? Nowhere in this code there's a function called nonexistant. Yet, you feed that into a compiler as the whole codebase and it will gladly accept it.
And the standard even says that it has to accept it. This is since there could be a T containing nonexistant somewhere. So if you instantiate the template with a type that has the static member function nonexistant it will compile and call the function. If you instantiate the template with a type that don't have the function, it won't compile.
As you can see, the name lookup is done during instantiation. This is called second phase name lookup. The second phase name lookup is done only during instantiation.
Now, enter if constexpr.
To make such construct working well with the rest of the language properly, it has been decided that if constexpr is defined as a branch for instantiation. As such, we can make some code non-instantiated, even in non templates!
extern int a;
void helper_1(int*);
void func_c() {
if constexpr (false) {
helper_1(&a);
}
}
The answer is that helper_1 and a are not ODR used. We could leave helper_1 and a not defined and there would not be linker errors.
Even better, the compiler won't instantiate templates that are in a discarded branch of a if constexpr:
template<typename T>
void helper_2() {
T::nonexistant();
}
void func_d() {
if constexpr (false) {
helper_2<int>();
}
}
This code won't compile with a normal if.
As you can see, the discarded branch of a if constexpr work just like a template that hasn't been instantiated, even in non template code.
Now let's mix it up:
template<typename T>
void func_b_3() {
if constexpr (false) {
nonexistant();
}
}
This is just like our template function in the beginning. We said that even if the template was not instantiated, the code was invalid, since the invalid expression don't depend on T. We also said that if constexpr is a branch in the instantiation process. The error happen before instantiation. This code won't compile either.
So finally, this code won't compile either:
void func_e() {
if constexpr (false) {
nonexistant();
}
}
Even though the content of the if constexpr is not instantiated, the error happen because the fist name lookup step is done, and the error happen before the instantiation process. It is just that in this case, there is no instantiation, but it doesn't matter at this point.
So what are the uses of if constexpr? Why does it seem to work only in templates?
The thing is, it doesn't work differently in templates. Just as we saw with func_b_3, the error still happen.
But, this case will work:
template<typename T>
void helper_3() {
if constexpr (false) {
T::nonexistant();
}
}
void func_f() {
helper_3<int>();
}
The expression int::nonexistant() is invalid, but the code compile. This is because since T::nonexistant() is an expression that depends on T, name lookup is done in the second phase. The second phase of name lookup is done during template instantiation. The if constexpr branch that contain T::nonexistant() is always the discarded part so the second phase of name lookup is never done.
There you go. if constexpr is not about not compiling a portion of code. Just like template, they are compiled and any expression that name lookup can be done is done. if constexpr is about controlling instantiation, even in non template function. All rules that applies to templates also applies to all branch of the if constexpr. Two phase name lookup still applies and allow programmers to not instantiate some part of the code that would otherwise not compile if instantiated.
So if a code cannot compiled in a template that is not instantiated, it won't compile in the branch of the if constexpr that is not instantiated.
Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive.
here
I am a little late to the party but a nice trick I use when I need if constexpr in a non template function is to wrap my code in a lambda.
struct Empty
{
};
void foo()
{
/* foo is not template, will not compile
if constexpr ( false )
{
std::cout << Empty{}.bar; // does not exist
}
*/
// now the code is in a lambda with auto param, so basicly a template method
[&](const auto& empty)
{
if constexpr ( false )
{
std::cout << empty.bar;
}
}(Empty{});
}
demo : https://wandbox.org/permlink/XEgZ6PXPLyyjXDlO
The fact I can use the [&] syntax make this solution way simpler to use than create an helper method since I don't need to forward all the parameters I need inside my if constexpr
I tried to play with the C++17 standard. I tried to use one of the features of C++17 if constexpr. And I had a problem... Please take a look at the following code. This compiles without errors. In the following code, I tried to use if constexpr to check if it is a pointer.
#include <iostream>
#include <type_traits>
template <typename T>
void print(T value)
{
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Ok
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
auto n = 1000;
print(n);
print(&n);
}
But when I rewrite the above code, as shown below, where if constexpr is in the main function:
#include <iostream>
#include <type_traits>
int main()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
I get a compilation error:
main.cpp:8:32: error: invalid type argument of unary ‘*’ (have ‘int’)
std::cout << "Ptr to " << *value << std::endl;
Problem is not in the main function. This can be any function similar to the following.
void print()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
print();
}
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
I would like to know why "if constexpr" works only in template functions, even if the type is deduced by the decltype from the input parameter.
This is by design.
if constexpr will not instantiate the branch not taken if it's within a template. It won't just treat the branch not taken as token soup and avoid parsing it or performing semantic analysis entirely. Both sides are still going to be analyzed, and since *value is ill-formed for ints, that's an error.
You simply can't use if constexpr to avoid compiling non-template code. It's only to avoid instantiating template code that's potentially invalid-for-the-particular-specialization.
C++ standard, clause 9.4.1:
If the if statement is of the form if constexpr, the value of the condition shall be a contextually converted constant expression of type bool (8.6); this form is called a constexpr if statement. If the value of the converted
condition is false, the first substatement is a discarded statement, otherwise the second substatement, if present, is a discarded statement. During the instantiation of an enclosing templated entity (Clause 17), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.
(emphasis mine)
So, a substatement of a constexpr if still gets instantiated if it is not inside a template, thus it must at least compile.
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
The thing is, it also work in non template, just not in the way you would expect.
For if constexpr to work like you stated, you not only need a template, but you need the contained expressions to be dependent on the template parameters.
Let's go step by step why this is made that way in C++, and what are the implications.
Let's start simple. Does the following code compile?
void func_a() {
nonexistant();
}
I think we will all agree that it won't compile, we are trying to use a function that hasn't been declared.
Let's add one layer.
Does the following code compile?
template<typename T>
void func_b_1() {
nonexistant();
}
With a correct compiler, this will not compile.
But why is that? You could argue that this code is never actually compiled, since the template is never instantiated.
The standard define something they call two phase name lookup. This is that even if the template is not instantiated, the compiler must perform name lookup an anything that don't depend on the template parameter.
And that make sense. If the expression nonexistant() don't depend on T, why would its meaning change with T? Hence, this expression is the same as in func_a in the eye of the compiler.
So how about dependent names?
template<typename T>
void func_b_2() {
T::nonexistant();
}
This will compile! Why is that? Nowhere in this code there's a function called nonexistant. Yet, you feed that into a compiler as the whole codebase and it will gladly accept it.
And the standard even says that it has to accept it. This is since there could be a T containing nonexistant somewhere. So if you instantiate the template with a type that has the static member function nonexistant it will compile and call the function. If you instantiate the template with a type that don't have the function, it won't compile.
As you can see, the name lookup is done during instantiation. This is called second phase name lookup. The second phase name lookup is done only during instantiation.
Now, enter if constexpr.
To make such construct working well with the rest of the language properly, it has been decided that if constexpr is defined as a branch for instantiation. As such, we can make some code non-instantiated, even in non templates!
extern int a;
void helper_1(int*);
void func_c() {
if constexpr (false) {
helper_1(&a);
}
}
The answer is that helper_1 and a are not ODR used. We could leave helper_1 and a not defined and there would not be linker errors.
Even better, the compiler won't instantiate templates that are in a discarded branch of a if constexpr:
template<typename T>
void helper_2() {
T::nonexistant();
}
void func_d() {
if constexpr (false) {
helper_2<int>();
}
}
This code won't compile with a normal if.
As you can see, the discarded branch of a if constexpr work just like a template that hasn't been instantiated, even in non template code.
Now let's mix it up:
template<typename T>
void func_b_3() {
if constexpr (false) {
nonexistant();
}
}
This is just like our template function in the beginning. We said that even if the template was not instantiated, the code was invalid, since the invalid expression don't depend on T. We also said that if constexpr is a branch in the instantiation process. The error happen before instantiation. This code won't compile either.
So finally, this code won't compile either:
void func_e() {
if constexpr (false) {
nonexistant();
}
}
Even though the content of the if constexpr is not instantiated, the error happen because the fist name lookup step is done, and the error happen before the instantiation process. It is just that in this case, there is no instantiation, but it doesn't matter at this point.
So what are the uses of if constexpr? Why does it seem to work only in templates?
The thing is, it doesn't work differently in templates. Just as we saw with func_b_3, the error still happen.
But, this case will work:
template<typename T>
void helper_3() {
if constexpr (false) {
T::nonexistant();
}
}
void func_f() {
helper_3<int>();
}
The expression int::nonexistant() is invalid, but the code compile. This is because since T::nonexistant() is an expression that depends on T, name lookup is done in the second phase. The second phase of name lookup is done during template instantiation. The if constexpr branch that contain T::nonexistant() is always the discarded part so the second phase of name lookup is never done.
There you go. if constexpr is not about not compiling a portion of code. Just like template, they are compiled and any expression that name lookup can be done is done. if constexpr is about controlling instantiation, even in non template function. All rules that applies to templates also applies to all branch of the if constexpr. Two phase name lookup still applies and allow programmers to not instantiate some part of the code that would otherwise not compile if instantiated.
So if a code cannot compiled in a template that is not instantiated, it won't compile in the branch of the if constexpr that is not instantiated.
Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive.
here
I am a little late to the party but a nice trick I use when I need if constexpr in a non template function is to wrap my code in a lambda.
struct Empty
{
};
void foo()
{
/* foo is not template, will not compile
if constexpr ( false )
{
std::cout << Empty{}.bar; // does not exist
}
*/
// now the code is in a lambda with auto param, so basicly a template method
[&](const auto& empty)
{
if constexpr ( false )
{
std::cout << empty.bar;
}
}(Empty{});
}
demo : https://wandbox.org/permlink/XEgZ6PXPLyyjXDlO
The fact I can use the [&] syntax make this solution way simpler to use than create an helper method since I don't need to forward all the parameters I need inside my if constexpr
I'd like to add compile-time checking for different meanings of double. In the real world, I'm trying to make sure all calculations are done in consistent units. For the purposes of this question, I've concocted a toy example in which numbers have flavors.
I've been trying to achieve this based on a template parameter. Using the c++0x aliases feature described in another answer, I declared a Number<Flavor> as:
enum Flavor { Cherry, Plum, Raspberry };
template <Flavor> using Number = double;
This gives me the ability to declare local variables or parameters as particular flavors of Number, then use those variables as ordinary doubles in most contexts.
My problem is, I can't find a way to declare a function that will only accept a particular flavor as its argument:
void printCherryNumber(Number<Cherry> num) { cout << num << endl; }
int main() {
Number<Cherry> a(5);
Number<Plum> b(6);
Number<Raspberry> c(3.1415);
printCherryNumber(a);
printCherryNumber(b); // O, if only this could be a compiler error.
return 0;
}
My goal is to make printCherryNumber(b) fail to compile because b is a Number<Plum> not Number<Cherry>. Many existing questions tackle variations on this problem with solutions that seem to not work on the type alias construct I've used for Number.
Stuff I've Tried
From this answer, I see the suggestion to add a templated version of the function that explicitly does nothing or breaks, as in
template <typename T> void printCherryNumber(T num) = delete;
This has no effect at all, and why should it? Number<Plum> is really double and Number<Cherry> is also double so the compiler never bothers with the templated version.
Another answer suggests using a single templated function and static asserts, as in:
template <Flavor F> void printPlumNumber(Number<F> num) {
static_assert(F == Plum, "Wrong number flavor!");
cout << num << endl;
}
This fails because regardless of the actual value of F, Number<F> is still just double and so I get an error about not being able to infer the value of F.
Elsewhere someone suggests explicit specialization, which also fails for this case:
template <Flavor F> void printRaspberryNumber(Number<F> num) = delete;
template <> void printRaspberryNumber<Raspberry>(Number<Raspberry> num) {
cout << num << endl;
}
Here, the compiler treats the call as ambiguous, in part again because it can't infer a value for F.
Elephant in the Room
I could, of course, make Number a single-value struct in the form of
template <Flavor> struct Number { double value; };
but I'm trying to avoid this option because I'm not terribly thrilled about the idea of having .value all over everywhere in my code, nor am I especially eager to define operators for Number that just proxy down to double.
Obligatory ideone
http://ideone.com/4HiYtI
The problem with this approach:
enum Flavor { Cherry, Plum, Raspberry };
template <Flavor> using Number = double;
is that alias templates are transparent. Number<Cherry>, Number<Plum>, and double are all the same type. That doesn't solve your problem at all.
What you want is typically called an opaque typedef. You really do want your last option:
template <Flavor>
struct Number {
double value;
operator double() const { return value; } // for convenience
Number& operator=(double ); // if necessary
// possibly more operations
};
This way, Number<Cherry> and Number<Plum> are different types. They are not convertible to each other. And double is not implicitly convertible to either.
You can also take a look at BOOST_STRONG_TYPEDEF and its implementation, it's intended to solve this problem too.
The option you're trying to avoid is really the only way to do this.
A template alias is what it is: an alias. A template alias is equivalent to the underlying type. In all respects.
template <Flavor> using Number = double;
This means that Number<Flavor> is a double. It is not anything else. Number<Plum> is a double, too. This is pretty much the same as doing a global search/replace of either one of them to double. The end result will be identical. The type is exactly the same.
You can only "declare a function that will only accept" a specific type. Except using a template alias, the template alias is the same type, as such it is not possible to declare a function that accept a double, but does not accept a double. It is a logical falsity.
Wrapping a double in a struct is the only way to achieve strict type checking, of this kind. It's not that bad. Toss in a few overloads, a few operators, and your wrapped struct will enforce strict type checking, and the compiler is likely to produce identical code, with no runtime penalty.