Reputation: 869
I have a function template forwardMaybeNull
. I hope it could forward the first argument if it's not a nullptr
and just returns the second argument if the first argument is a nullptr
.
template <typename T, typename U,
std::enable_if_t<std::is_same<T, std::nullptr_t>::value>...>
U&& forwardMaybeNull(std::nullptr_t, U&& rvalue_default) {
return std::move(rvalue_default);
}
template <typename T, typename U,
std::enable_if_t<!std::is_same<T, std::nullptr_t>::value>...>
T&& forwardMaybeNull(std::remove_reference_t<T>& arg, U&&) {
return static_cast<T&&>(arg);
}
template <typename T, typename U,
std::enable_if_t<!std::is_same<T, std::nullptr_t>::value>...>
T&& forwardMaybeNull(std::remove_reference_t<T>&& arg, U&&) {
return static_cast<T&&>(arg);
}
template <typename T>
void Bar(T&&) {}
template <typename T>
void Foo(T&& t) {
Bar(forwardMaybeNull<T>(t, [](){}));
}
int main() {
Foo(nullptr);
}
It works fine in gcc4.8 but VS2013 says it's a "ambiguous call to overloaded function".
Upvotes: 4
Views: 632
Reputation: 5552
I think the problem is due to this bug https://connect.microsoft.com/VisualStudio/feedback/details/845750/broken-using-template-in-vs2013-update-2-rc. If you use enable_if rather than enable_if_t it should work. Also its usually a good idea to use an unnamed default template parameter rather than a variadic pack for SFINAE in function templates.
I have used this pattern quite a lot in MSVC2013 update 2 with no real problems:
template <typename T, typename U, typename = typename std::enable_if<std::is_same<
T, std::nullptr_t>::value>::type>
U&& forwardMaybeNull(std::nullptr_t, U&& rvalue_default) {
return std::move(rvalue_default);
}
Although the offending alias bug was fixed in MSVC2013 update 3 they essentially broke initializer lists (http://connect.microsoft.com/VisualStudio/feedbackdetail/view/938122/list-initialization-inside-member-initializer-list-or-non-static-data-member-initializer-is-not-implemented) so its probably not a good idea to upgrade.
In this particular case I would suggest using tag dispatch rather than SFINAE to achieve static dispatch:
template <typename T, typename U>
U&& forwardMaybeNull(std::true_type, T, U&& rvalue_default) {
return std::move(rvalue_default);
}
template <typename T, typename U>
T&& forwardMaybeNull(std::false_type,T&& arg, U&&) {
return std::forward<T>(arg);
}
template <typename T>.
void Foo(T&& t) {
Bar(forwardMaybeNull(typename std::is_same<T, std::nullptr_t>::type{}, std::forward<T>(t), [](){}));
}
Upvotes: 0
Reputation: 234504
I recommend avoiding any non-trivial template code in VS2013. Hell, even template code that I would call trivial has given me trouble.
For this case you can resort to the old technique of partially specialising a class template. I would actually do this even in GCC. Something like the following.
namespace detail {
template <typename T, typename U>
struct forwarderMaybeNull {
using result_type = T&&;
static T&& call(std::remove_reference_t<T>& arg, U&&) {
return static_cast<T&&>(arg);
}
static T&& call(std::remove_reference_t<T>&& arg, U&&) {
return static_cast<T&&>(arg);
}
};
template <typename U>
struct forwarderMaybeNull<nullptr_t, U> {
using result_type = U&&;
static U&& call(std::nullptr_t, U&& rvalue_default) {
return std::move(rvalue_default);
}
};
}
template <typename T, typename U>
typename detail::forwarderMaybeNull<T, U>::result_type forwardMaybeNull(
std::remove_reference_t<T>& arg, U&& u) {
return detail::forwarderMaybeNull<T, U>::call(std::forward<T>(arg),
std::forward<U>(u));
}
template <typename T, typename U>
typename detail::forwarderMaybeNull<T, U>::result_type forwardMaybeNull(
std::remove_reference_t<T>&& arg, U&& u) {
return detail::forwarderMaybeNull<T, U>::call(std::forward<T>(arg),
std::forward<U>(u));
}
Upvotes: 5