Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Russell's paradox in C++ templates [duplicate]

Tags:

Consider this program:

#include <iostream>
#include <type_traits>

using namespace std;

struct russell {
    template <typename barber, 
              typename = typename enable_if<!is_convertible<barber, russell>::value>::type>
    russell(barber) {}
};

russell verify1() { return 42L; }
russell verify2() { return 42; }

int main ()
{
    verify1();
    verify2();
    cout << is_convertible<long, russell>::value;
    cout << is_convertible<int, russell>::value;
    return 0;
}

If some type barber is not convertible to russell. we attempt to create a paradox by making it convertible (enabling a converting constructor).

The output is 00 with three popular compilers, though constructors are evidently working.

I suspect the behaviour should be undefined, but cannot find anything in the standard.

What should the output of this program be, and why?

like image 980
n. 1.8e9-where's-my-share m. Avatar asked Aug 27 '17 16:08

n. 1.8e9-where's-my-share m.


1 Answers

During overload resolution, template argument deduction must instantiate the default argument to obtain a complete set of template arguments to instantiate the function template with (if possible). Hence the instantiation of is_convertible<int, russell> is necessitated, which internally invokes overload resolution. The constructor template in russell is in scope in the instantiation context of the default template argument.

The crux is that is_convertible<int, russell>::value evaluates the default template argument of russell, which itself names is_convertible<int, russell>::value.

is_convertible<int, russell>::value
              |
              v
russell:russell(barber)
              |
              v
is_convertible<int, russell>::value (not in scope)

core issue 287's (unadopted) resolution seems to be the de facto rule abode by major compilers. Because the point of instantiation comes right before an entity, value's declaration is not in scope while we're evaluating its initialiser; hence our constructor has a substitution failure and is_convertible in main yields false. Issue 287 clarifies which declarations are in scope, and which are not, namely value.

Clang and GCC do slightly differ on how they treat this situation. Take this example with a custom, transparent implementation of the trait:

#include <type_traits>

template <typename T, typename U>
struct is_convertible
{
    static void g(U);

    template <typename From>
    static decltype(g(std::declval<From>()), std::true_type{}) f(int);
    template <typename>
    static std::false_type f(...);

    static const bool value = decltype(f<T>()){};
};

struct russell
{
    template <typename barber,
              typename = std::enable_if_t<!is_convertible<barber, russell>::value>>
    russell(barber) {}
};

russell foo() { return 42; }

int main() {}

Clang translates this silently. GCC complains about an infinite recursion chain: it seems to argue that value is indeed in scope in the recursive instantiation of the default argument, and so proceeds to instantiate the initializer of value again and again. However, arguably Clang is in the right, since both the current and the drafted relevant phrase in [temp.point]/4 mandate that the PoI is before the nearest enclosing declaration. I.e. that very declaration is not considered to be part of the partial instantiation (yet). Kinda makes sense if you consider the above scenario. Workaround for GCC: employ a declaration form in which the name is not declared until after the initializer is instantiated.

enum {value = decltype(f<T>()){}};

This compiles with GCC as well.

like image 86
Columbo Avatar answered Sep 18 '22 05:09

Columbo