Tom Knapen
Tom Knapen

Reputation: 2277

sfinae ambiguous call when passing 0 instead of 1

Relevant code:

#include <iostream>
#include <type_traits>

template<template<typename...> class C, typename... T>
struct is_valid_instantiation_impl
{
  // Default constructor
  template<template<typename...> class D>
  static std::true_type test(decltype(D<T...>{ })*, int);
  // Copy constructor
  template<template<typename...> class D>
  static std::true_type test(decltype(D<T...>{ std::declval<const D<T...>&>() })*, long);
  // Move constructor
  template<template<typename...> class D>
  static std::true_type test(decltype(D<T...>{ std::declval<D<T...>&&>() })*, int*);

  template<template<typename...> class D>
  static std::false_type test(...);

  using type =  decltype(test<C>(nullptr, 1));
  //                                      ^ this one
};

template<template<typename...> class C, typename... T>
struct is_valid_instantiation : is_valid_instantiation_impl<C, T...>::type { };

template<typename>
struct tester;

template<>
struct tester<int>
{
  tester(int);
};

int main(int argc, char** argv)
{
  std::cout << "instantiable<int>: " << is_valid_instantiation<tester, int>::value << std::endl;
}

This code compiles fine, but when I replace that 1 by a 0 I get (Clang 3.4)

xxx: error: call to 'test' is ambiguous
  using type =  decltype(test<C>(nullptr, 0));
                         ^~~~~~~
xxx: note: in instantiation of template class 'is_valid_instantiation_impl<tester, int>' requested here
...

xxx: note: candidate function [with D = tester]
  static std::true_type test(decltype(D<T...>{ std::declval<const D<T...>&>() })*, long);
                        ^
xxx: note: candidate function [with D = tester]
  static std::true_type test(decltype(D<T...>{ std::declval<D<T...>&&>() })*, int*);
                        ^
xxx: note: candidate function [with D = tester]
  static std::false_type test(...);
                         ^

Why is it that suddenly this call is ambiguous? As far as I know 0 is still an int (and GCC 4.8.2 seems to agree with me, but won't compile it either):

xxx: error: call of overloaded ‘test(std::nullptr_t, int)’ is ambiguous

EDIT:

Please note I'm not asking how to resolve this ambiguity, but why there is an ambiguity when I use 0 instead of 1. See this live example for the full code showing the error.

Upvotes: 1

Views: 261

Answers (2)

Mark Morrison
Mark Morrison

Reputation: 101

In some C++ compilers (most? I'm not sure), nullptr is defined as 0. If you're using managed pointers, then nullptr and 0 are defined differently. But for native pointers, they're the same thing.

So your code is hitting this literal 0, and the compiler suddenly doesn't know whether that's supposed to be a pointer or an integer, because technically it could be either. Whereas "1" is defined in C++ to be a literal int, so there's no confusion.

Unfortunately, I don't see any way around it other than a cast to explicitly tell the compiler what 0 is supposed to mean.

Upvotes: 0

Barry
Barry

Reputation: 303347

I don't really know what your C and D are, but basically you have these two overloads:

static std::true_type test(X*, long);
static std::true_type test(Y*, int*);

test(nullptr, 0);

nullptr matches both of the first arguments, and 0 matches both of the second. Both overloads are viable, and neither is a better candidate than the other. Hence:

error: call of overloaded ‘test(std::nullptr_t, int)’ is ambiguous

The reason that 0 can pass for int* is because, from [conv.ptr]:

A null pointer constant is an integer literal with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type.

Upvotes: 1

Related Questions