I'm trying to add function overload for a function template based on the type of one of the template parameters and passing an argument defined as consexpr somewhere else to aid in overload resolution.
Here's a code sample for what I'm trying to achieve:
struct use_my_impl_t {};
constexpr use_my_impl_t use_my_impl;
template<
typename T,
typename std::enable_if<std::is_same<T, use_my_impl_t>::value>::type * = nullptr>
void foo(int i, T&& t)
{
std::cout << "Using my impl: " << --i << std::endl;
}
template<
typename T,
typename std::enable_if<!std::is_same<T, use_my_impl_t>::value>::type * = nullptr>
void foo(int i, T&& t)
{
std::cout << "Using default impl: " << ++i << std::endl;
}
So when I pass an instance of use_my_impl_t
as a second argument it should use my implementation, and for all other cases it should use the default impl. The result is not what I would expect though. If I call like like this:
constexpr use_my_impl_t use_my_impl;
foo(42, use_my_impl_t{});
foo(42, 2);
foo(42, use_my_impl);
it prints
Using my impl: 41
Using default impl: 43
Using default impl: 43
Why the last statement doesn't invoke my implementation despite an instance of use_my_impl_t
being passed?
The issue here is what T
is being deduced as. In foo(42, use_my_impl_t{});
, use_my_impl_t{}
is an rvalue so T
gets deduced as use_my_impl_t
and the parameter becomes use_my_impl_t&&
.
With foo(42, use_my_impl);
, use_my_impl
is an lvalue. That means T
is deduced as use_my_impl_t &
to preserve that lvalueness. That means the parameter becomes use_my_impl_t& &&
which collapses to use_my_impl_t&
.
This is what is causing std::is_same<T, use_my_impl_t>::value
not to work, since a use_my_impl_t&
is not the same as a use_my_impl_t
. To fix this you can use std::decay_t
to remove the reference and cv-qualification of the type. That would give you
template<
typename T,
typename std::enable_if<std::is_same<std::decay_t<T>, use_my_impl_t>::value>::type * = nullptr>
void foo(int i, T&& t)
{
std::cout << "Using my impl: " << --i << std::endl;
}
template<
typename T,
typename std::enable_if<!std::is_same<std::decay_t<T>, use_my_impl_t>::value>::type * = nullptr>
void foo(int i, T&& t)
{
std::cout << "Using default impl: " << ++i << std::endl;
}
which then outputs
Using my impl: 41
Using default impl: 43
Using my impl: 41
using your driver code.