QingYun
QingYun

Reputation: 869

SFINAE in VS2013

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

Answers (2)

odinthenerd
odinthenerd

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

R. Martinho Fernandes
R. Martinho Fernandes

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

Related Questions