Trying to specialize a template function based on the presence of a typedef within class
up vote
0
down vote
favorite
I want to be able to customize handling of a struct based on the presence of a type within the struct (without writing any additional code per custom struct), like:
struct Normal_t
{
};
struct Custom_t
{
using my_custom_type = bool;
};
It seems like I should be able to do something like this, but it doesn't work:
template <class T, typename Enabler = void>
struct has_custom_type
{
bool operator()() { return false; }
};
template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
bool operator()() { return true; }
};
bool b_normal = has_custom_type<Normal_t>()(); // returns false
bool b_custom = has_custom_type<Custom_t>()(); // returns false, INCORRECT? should return true?
What I don't understand is that the standard library uses something similar but seemingly more convoluted for its type traits. For example, this works:
template<bool test, class T = void>
struct my_enable_if
{
};
template<class T>
struct my_enable_if<true, T>
{
using type = T;
};
template <class T, class Enabler = void>
struct foo
{
bool operator()() { return false; }
};
template <class T>
struct foo<T, typename my_enable_if<std::is_integral<T>::value>::type>
{
bool operator()() { return true; }
};
bool foo_float = foo<float>()(); // returns false
bool foo_int = foo<int>()(); // returns true
In both cases, the specialization is happening based on the presence of a type within a struct, in one case typename T::my_custom_type
and in the other typename my_enable_if<std::is_integral<T>::value>::type
. Why does the second version work but not the first?
I came up with this workaround using the ... parameter pack syntax, but I'd really like to understand if there is a way to do this using normal template specialization without using the parameter pack syntax, and if not, why.
template<typename ...Args>
bool has_custom_type_2(Args&& ...args) { return false; }
template<class T, std::size_t = sizeof(T::my_custom_type)>
bool has_custom_type_2(T&) { return true; }
template<class T, std::size_t = sizeof(T::my_custom_type)>
bool has_custom_type_2(T&&) { return true; } /* Need this T&& version to handle has_custom_type_2(SomeClass()) where the parameter is an rvalue */
bool b2_normal = has_custom_type_2(Normal_t()); // returns false
bool b2_custom = has_custom_type_2(Custom_t()); // returns true - CORRECT!
c++ templates variadic-templates sfinae template-specialization
add a comment |
up vote
0
down vote
favorite
I want to be able to customize handling of a struct based on the presence of a type within the struct (without writing any additional code per custom struct), like:
struct Normal_t
{
};
struct Custom_t
{
using my_custom_type = bool;
};
It seems like I should be able to do something like this, but it doesn't work:
template <class T, typename Enabler = void>
struct has_custom_type
{
bool operator()() { return false; }
};
template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
bool operator()() { return true; }
};
bool b_normal = has_custom_type<Normal_t>()(); // returns false
bool b_custom = has_custom_type<Custom_t>()(); // returns false, INCORRECT? should return true?
What I don't understand is that the standard library uses something similar but seemingly more convoluted for its type traits. For example, this works:
template<bool test, class T = void>
struct my_enable_if
{
};
template<class T>
struct my_enable_if<true, T>
{
using type = T;
};
template <class T, class Enabler = void>
struct foo
{
bool operator()() { return false; }
};
template <class T>
struct foo<T, typename my_enable_if<std::is_integral<T>::value>::type>
{
bool operator()() { return true; }
};
bool foo_float = foo<float>()(); // returns false
bool foo_int = foo<int>()(); // returns true
In both cases, the specialization is happening based on the presence of a type within a struct, in one case typename T::my_custom_type
and in the other typename my_enable_if<std::is_integral<T>::value>::type
. Why does the second version work but not the first?
I came up with this workaround using the ... parameter pack syntax, but I'd really like to understand if there is a way to do this using normal template specialization without using the parameter pack syntax, and if not, why.
template<typename ...Args>
bool has_custom_type_2(Args&& ...args) { return false; }
template<class T, std::size_t = sizeof(T::my_custom_type)>
bool has_custom_type_2(T&) { return true; }
template<class T, std::size_t = sizeof(T::my_custom_type)>
bool has_custom_type_2(T&&) { return true; } /* Need this T&& version to handle has_custom_type_2(SomeClass()) where the parameter is an rvalue */
bool b2_normal = has_custom_type_2(Normal_t()); // returns false
bool b2_custom = has_custom_type_2(Custom_t()); // returns true - CORRECT!
c++ templates variadic-templates sfinae template-specialization
add a comment |
up vote
0
down vote
favorite
up vote
0
down vote
favorite
I want to be able to customize handling of a struct based on the presence of a type within the struct (without writing any additional code per custom struct), like:
struct Normal_t
{
};
struct Custom_t
{
using my_custom_type = bool;
};
It seems like I should be able to do something like this, but it doesn't work:
template <class T, typename Enabler = void>
struct has_custom_type
{
bool operator()() { return false; }
};
template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
bool operator()() { return true; }
};
bool b_normal = has_custom_type<Normal_t>()(); // returns false
bool b_custom = has_custom_type<Custom_t>()(); // returns false, INCORRECT? should return true?
What I don't understand is that the standard library uses something similar but seemingly more convoluted for its type traits. For example, this works:
template<bool test, class T = void>
struct my_enable_if
{
};
template<class T>
struct my_enable_if<true, T>
{
using type = T;
};
template <class T, class Enabler = void>
struct foo
{
bool operator()() { return false; }
};
template <class T>
struct foo<T, typename my_enable_if<std::is_integral<T>::value>::type>
{
bool operator()() { return true; }
};
bool foo_float = foo<float>()(); // returns false
bool foo_int = foo<int>()(); // returns true
In both cases, the specialization is happening based on the presence of a type within a struct, in one case typename T::my_custom_type
and in the other typename my_enable_if<std::is_integral<T>::value>::type
. Why does the second version work but not the first?
I came up with this workaround using the ... parameter pack syntax, but I'd really like to understand if there is a way to do this using normal template specialization without using the parameter pack syntax, and if not, why.
template<typename ...Args>
bool has_custom_type_2(Args&& ...args) { return false; }
template<class T, std::size_t = sizeof(T::my_custom_type)>
bool has_custom_type_2(T&) { return true; }
template<class T, std::size_t = sizeof(T::my_custom_type)>
bool has_custom_type_2(T&&) { return true; } /* Need this T&& version to handle has_custom_type_2(SomeClass()) where the parameter is an rvalue */
bool b2_normal = has_custom_type_2(Normal_t()); // returns false
bool b2_custom = has_custom_type_2(Custom_t()); // returns true - CORRECT!
c++ templates variadic-templates sfinae template-specialization
I want to be able to customize handling of a struct based on the presence of a type within the struct (without writing any additional code per custom struct), like:
struct Normal_t
{
};
struct Custom_t
{
using my_custom_type = bool;
};
It seems like I should be able to do something like this, but it doesn't work:
template <class T, typename Enabler = void>
struct has_custom_type
{
bool operator()() { return false; }
};
template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
bool operator()() { return true; }
};
bool b_normal = has_custom_type<Normal_t>()(); // returns false
bool b_custom = has_custom_type<Custom_t>()(); // returns false, INCORRECT? should return true?
What I don't understand is that the standard library uses something similar but seemingly more convoluted for its type traits. For example, this works:
template<bool test, class T = void>
struct my_enable_if
{
};
template<class T>
struct my_enable_if<true, T>
{
using type = T;
};
template <class T, class Enabler = void>
struct foo
{
bool operator()() { return false; }
};
template <class T>
struct foo<T, typename my_enable_if<std::is_integral<T>::value>::type>
{
bool operator()() { return true; }
};
bool foo_float = foo<float>()(); // returns false
bool foo_int = foo<int>()(); // returns true
In both cases, the specialization is happening based on the presence of a type within a struct, in one case typename T::my_custom_type
and in the other typename my_enable_if<std::is_integral<T>::value>::type
. Why does the second version work but not the first?
I came up with this workaround using the ... parameter pack syntax, but I'd really like to understand if there is a way to do this using normal template specialization without using the parameter pack syntax, and if not, why.
template<typename ...Args>
bool has_custom_type_2(Args&& ...args) { return false; }
template<class T, std::size_t = sizeof(T::my_custom_type)>
bool has_custom_type_2(T&) { return true; }
template<class T, std::size_t = sizeof(T::my_custom_type)>
bool has_custom_type_2(T&&) { return true; } /* Need this T&& version to handle has_custom_type_2(SomeClass()) where the parameter is an rvalue */
bool b2_normal = has_custom_type_2(Normal_t()); // returns false
bool b2_custom = has_custom_type_2(Custom_t()); // returns true - CORRECT!
c++ templates variadic-templates sfinae template-specialization
c++ templates variadic-templates sfinae template-specialization
edited Nov 10 at 19:23
max66
33.1k63660
33.1k63660
asked Oct 24 at 19:37
James Thrush
564
564
add a comment |
add a comment |
3 Answers
3
active
oldest
votes
up vote
2
down vote
accepted
The problem is that you specify default void
type for Enabler
, but T::my_custom_type
is not void
. Either use bool
as default type, or use std::void_t
that always returns void
:
template <class T, typename = void>
struct has_custom_type : std::false_type { };
template <class T>
struct has_custom_type<T, std::void_t<typename T::my_custom_type>> : std::true_type { };
This answer explains why types should match.
1
Thank you! That is exactly what I wanted. Changing that default type was the one thing I hadn't tried. Thank you also for pointing out std::void_t and the use of std::true_type/false_type. I think I now understand why the types need to match: when the template is instantiated byhas_custom_type<Custom_t>
, the compiler first only looks at the base template and says "ok, the template parameters are set to <Custom_t,void>", and only after that does it look for specializations that match and are more specific.
– James Thrush
Oct 25 at 7:20
1
@JamesThrush, exactly.
– Evg
Oct 25 at 8:25
add a comment |
up vote
2
down vote
As explained by others, if you set a void
default value for the second template parameter, your solution works only if my_custom_type
is void
.
If my_custom_type
is bool
, you can set bool
the default value. But isn't a great solution because loose generality.
To be more general, you can use SFINAE through something that fail if my_custom_type
doesn't exist but return ever the same type (void
, usually) if my_custom_type
is present.
Pre C++17 you can use decltype()
, std::declval
and the power of comma operator
template <class T, typename Enabler = void>
struct has_custom_type
{ bool operator()() { return false; } };
template <class T>
struct has_custom_type<T,
decltype( std::declval<typename T::my_custom_type>(), void() )>
{ bool operator()() { return true; } };
Starting from C++17 it's simpler because you can use std::void_t
(see Evg's answer, also for the use of std::true_type
and std::false_type
instead of defining an operator()
).
add a comment |
up vote
1
down vote
template <class T, typename Enabler = void> // <== void set as default template parameter type
struct has_custom_type
{
bool operator()() { return false; }
};
template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
bool operator()() { return true; }
};
The specialization matches when it gets the template parameters <T, bool>
. However, when you just specify <T>
, without a second type, then it goes to the default type you specified =void
to come up with the call <T, void>
, which doesn't match your bool
specialization.
Live example showing it matches with explicit <T, bool>
: https://godbolt.org/z/MEJvwT
add a comment |
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
The problem is that you specify default void
type for Enabler
, but T::my_custom_type
is not void
. Either use bool
as default type, or use std::void_t
that always returns void
:
template <class T, typename = void>
struct has_custom_type : std::false_type { };
template <class T>
struct has_custom_type<T, std::void_t<typename T::my_custom_type>> : std::true_type { };
This answer explains why types should match.
1
Thank you! That is exactly what I wanted. Changing that default type was the one thing I hadn't tried. Thank you also for pointing out std::void_t and the use of std::true_type/false_type. I think I now understand why the types need to match: when the template is instantiated byhas_custom_type<Custom_t>
, the compiler first only looks at the base template and says "ok, the template parameters are set to <Custom_t,void>", and only after that does it look for specializations that match and are more specific.
– James Thrush
Oct 25 at 7:20
1
@JamesThrush, exactly.
– Evg
Oct 25 at 8:25
add a comment |
up vote
2
down vote
accepted
The problem is that you specify default void
type for Enabler
, but T::my_custom_type
is not void
. Either use bool
as default type, or use std::void_t
that always returns void
:
template <class T, typename = void>
struct has_custom_type : std::false_type { };
template <class T>
struct has_custom_type<T, std::void_t<typename T::my_custom_type>> : std::true_type { };
This answer explains why types should match.
1
Thank you! That is exactly what I wanted. Changing that default type was the one thing I hadn't tried. Thank you also for pointing out std::void_t and the use of std::true_type/false_type. I think I now understand why the types need to match: when the template is instantiated byhas_custom_type<Custom_t>
, the compiler first only looks at the base template and says "ok, the template parameters are set to <Custom_t,void>", and only after that does it look for specializations that match and are more specific.
– James Thrush
Oct 25 at 7:20
1
@JamesThrush, exactly.
– Evg
Oct 25 at 8:25
add a comment |
up vote
2
down vote
accepted
up vote
2
down vote
accepted
The problem is that you specify default void
type for Enabler
, but T::my_custom_type
is not void
. Either use bool
as default type, or use std::void_t
that always returns void
:
template <class T, typename = void>
struct has_custom_type : std::false_type { };
template <class T>
struct has_custom_type<T, std::void_t<typename T::my_custom_type>> : std::true_type { };
This answer explains why types should match.
The problem is that you specify default void
type for Enabler
, but T::my_custom_type
is not void
. Either use bool
as default type, or use std::void_t
that always returns void
:
template <class T, typename = void>
struct has_custom_type : std::false_type { };
template <class T>
struct has_custom_type<T, std::void_t<typename T::my_custom_type>> : std::true_type { };
This answer explains why types should match.
edited Oct 24 at 20:02
answered Oct 24 at 19:46
Evg
3,74221334
3,74221334
1
Thank you! That is exactly what I wanted. Changing that default type was the one thing I hadn't tried. Thank you also for pointing out std::void_t and the use of std::true_type/false_type. I think I now understand why the types need to match: when the template is instantiated byhas_custom_type<Custom_t>
, the compiler first only looks at the base template and says "ok, the template parameters are set to <Custom_t,void>", and only after that does it look for specializations that match and are more specific.
– James Thrush
Oct 25 at 7:20
1
@JamesThrush, exactly.
– Evg
Oct 25 at 8:25
add a comment |
1
Thank you! That is exactly what I wanted. Changing that default type was the one thing I hadn't tried. Thank you also for pointing out std::void_t and the use of std::true_type/false_type. I think I now understand why the types need to match: when the template is instantiated byhas_custom_type<Custom_t>
, the compiler first only looks at the base template and says "ok, the template parameters are set to <Custom_t,void>", and only after that does it look for specializations that match and are more specific.
– James Thrush
Oct 25 at 7:20
1
@JamesThrush, exactly.
– Evg
Oct 25 at 8:25
1
1
Thank you! That is exactly what I wanted. Changing that default type was the one thing I hadn't tried. Thank you also for pointing out std::void_t and the use of std::true_type/false_type. I think I now understand why the types need to match: when the template is instantiated by
has_custom_type<Custom_t>
, the compiler first only looks at the base template and says "ok, the template parameters are set to <Custom_t,void>", and only after that does it look for specializations that match and are more specific.– James Thrush
Oct 25 at 7:20
Thank you! That is exactly what I wanted. Changing that default type was the one thing I hadn't tried. Thank you also for pointing out std::void_t and the use of std::true_type/false_type. I think I now understand why the types need to match: when the template is instantiated by
has_custom_type<Custom_t>
, the compiler first only looks at the base template and says "ok, the template parameters are set to <Custom_t,void>", and only after that does it look for specializations that match and are more specific.– James Thrush
Oct 25 at 7:20
1
1
@JamesThrush, exactly.
– Evg
Oct 25 at 8:25
@JamesThrush, exactly.
– Evg
Oct 25 at 8:25
add a comment |
up vote
2
down vote
As explained by others, if you set a void
default value for the second template parameter, your solution works only if my_custom_type
is void
.
If my_custom_type
is bool
, you can set bool
the default value. But isn't a great solution because loose generality.
To be more general, you can use SFINAE through something that fail if my_custom_type
doesn't exist but return ever the same type (void
, usually) if my_custom_type
is present.
Pre C++17 you can use decltype()
, std::declval
and the power of comma operator
template <class T, typename Enabler = void>
struct has_custom_type
{ bool operator()() { return false; } };
template <class T>
struct has_custom_type<T,
decltype( std::declval<typename T::my_custom_type>(), void() )>
{ bool operator()() { return true; } };
Starting from C++17 it's simpler because you can use std::void_t
(see Evg's answer, also for the use of std::true_type
and std::false_type
instead of defining an operator()
).
add a comment |
up vote
2
down vote
As explained by others, if you set a void
default value for the second template parameter, your solution works only if my_custom_type
is void
.
If my_custom_type
is bool
, you can set bool
the default value. But isn't a great solution because loose generality.
To be more general, you can use SFINAE through something that fail if my_custom_type
doesn't exist but return ever the same type (void
, usually) if my_custom_type
is present.
Pre C++17 you can use decltype()
, std::declval
and the power of comma operator
template <class T, typename Enabler = void>
struct has_custom_type
{ bool operator()() { return false; } };
template <class T>
struct has_custom_type<T,
decltype( std::declval<typename T::my_custom_type>(), void() )>
{ bool operator()() { return true; } };
Starting from C++17 it's simpler because you can use std::void_t
(see Evg's answer, also for the use of std::true_type
and std::false_type
instead of defining an operator()
).
add a comment |
up vote
2
down vote
up vote
2
down vote
As explained by others, if you set a void
default value for the second template parameter, your solution works only if my_custom_type
is void
.
If my_custom_type
is bool
, you can set bool
the default value. But isn't a great solution because loose generality.
To be more general, you can use SFINAE through something that fail if my_custom_type
doesn't exist but return ever the same type (void
, usually) if my_custom_type
is present.
Pre C++17 you can use decltype()
, std::declval
and the power of comma operator
template <class T, typename Enabler = void>
struct has_custom_type
{ bool operator()() { return false; } };
template <class T>
struct has_custom_type<T,
decltype( std::declval<typename T::my_custom_type>(), void() )>
{ bool operator()() { return true; } };
Starting from C++17 it's simpler because you can use std::void_t
(see Evg's answer, also for the use of std::true_type
and std::false_type
instead of defining an operator()
).
As explained by others, if you set a void
default value for the second template parameter, your solution works only if my_custom_type
is void
.
If my_custom_type
is bool
, you can set bool
the default value. But isn't a great solution because loose generality.
To be more general, you can use SFINAE through something that fail if my_custom_type
doesn't exist but return ever the same type (void
, usually) if my_custom_type
is present.
Pre C++17 you can use decltype()
, std::declval
and the power of comma operator
template <class T, typename Enabler = void>
struct has_custom_type
{ bool operator()() { return false; } };
template <class T>
struct has_custom_type<T,
decltype( std::declval<typename T::my_custom_type>(), void() )>
{ bool operator()() { return true; } };
Starting from C++17 it's simpler because you can use std::void_t
(see Evg's answer, also for the use of std::true_type
and std::false_type
instead of defining an operator()
).
edited Oct 24 at 19:57
answered Oct 24 at 19:51
max66
33.1k63660
33.1k63660
add a comment |
add a comment |
up vote
1
down vote
template <class T, typename Enabler = void> // <== void set as default template parameter type
struct has_custom_type
{
bool operator()() { return false; }
};
template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
bool operator()() { return true; }
};
The specialization matches when it gets the template parameters <T, bool>
. However, when you just specify <T>
, without a second type, then it goes to the default type you specified =void
to come up with the call <T, void>
, which doesn't match your bool
specialization.
Live example showing it matches with explicit <T, bool>
: https://godbolt.org/z/MEJvwT
add a comment |
up vote
1
down vote
template <class T, typename Enabler = void> // <== void set as default template parameter type
struct has_custom_type
{
bool operator()() { return false; }
};
template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
bool operator()() { return true; }
};
The specialization matches when it gets the template parameters <T, bool>
. However, when you just specify <T>
, without a second type, then it goes to the default type you specified =void
to come up with the call <T, void>
, which doesn't match your bool
specialization.
Live example showing it matches with explicit <T, bool>
: https://godbolt.org/z/MEJvwT
add a comment |
up vote
1
down vote
up vote
1
down vote
template <class T, typename Enabler = void> // <== void set as default template parameter type
struct has_custom_type
{
bool operator()() { return false; }
};
template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
bool operator()() { return true; }
};
The specialization matches when it gets the template parameters <T, bool>
. However, when you just specify <T>
, without a second type, then it goes to the default type you specified =void
to come up with the call <T, void>
, which doesn't match your bool
specialization.
Live example showing it matches with explicit <T, bool>
: https://godbolt.org/z/MEJvwT
template <class T, typename Enabler = void> // <== void set as default template parameter type
struct has_custom_type
{
bool operator()() { return false; }
};
template <class T>
struct has_custom_type<T, typename T::my_custom_type>
{
bool operator()() { return true; }
};
The specialization matches when it gets the template parameters <T, bool>
. However, when you just specify <T>
, without a second type, then it goes to the default type you specified =void
to come up with the call <T, void>
, which doesn't match your bool
specialization.
Live example showing it matches with explicit <T, bool>
: https://godbolt.org/z/MEJvwT
answered Oct 24 at 19:49
xaxxon
14.3k43058
14.3k43058
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52976727%2ftrying-to-specialize-a-template-function-based-on-the-presence-of-a-typedef-with%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown