Подтвердить что ты не робот

Парадокс Рассела в шаблонах С++

Рассмотрим эту программу:

#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;
}

Если какой-либо тип barber не конвертируется в russell. мы пытаемся создать парадокс, сделав его конвертируемым (включив конструктор преобразования).

Вывод 00 с тремя популярными компиляторами, хотя конструкторы, очевидно, работают.

Я подозреваю, что поведение должно быть undefined, но не может найти ничего в стандарте.

Каким должен быть выход этой программы и почему?

4b9b3361

Ответ 1

Во время разрешения перегрузки вывод аргумента шаблона должен создавать экземпляр аргумента по умолчанию для получения полного набора аргументов шаблона для создания экземпляра шаблона функции (если это возможно). Следовательно, требуется создание is_convertible<int, russell>, которое внутренне вызывает разрешение перегрузки. Шаблон конструктора в russell находится в области контекста контекста шаблона по умолчанию.

С основной точки зрения, относящейся к нашим конкретным реализациям библиотек, основная проблема 287 (неадекватная) резолюция/де-факто интерпретация дает нам когерентный подход к этой проблеме, который, по-видимому, реализует основные компиляторы mutatis mutandis. Предполагая, что реализация std::is_convertible непреднамеренно инициализирует value с помощью некоторого основного конструктора и dagger;value Объявление не входит в область действия при выводе аргумента шаблона для russell::russell через is_convertible::value initializer, следовательно, конструктор не является кандидатом (сбой замены) и is_convertible дает false. В выпуске 287 разъясняется, какие объявления находятся в области действия, а какие нет, а именно value. Это прагматическое объяснение. Более всеобъемлющий подход будет полагаться на консенсус LWG относительно такого использования is_convertible, который предположительно будет считаться плохо сформированным NDR/UB.

Clang и GCC немного отличаются от того, как они относятся к этой ситуации. Возьмите этот пример с помощью пользовательской прозрачной реализации признака:

#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 переводит это молча. GCC жалуется на бесконечную цепочку рекурсии: кажется, утверждают, что value действительно находится в области рекурсивного экземпляра аргумента по умолчанию, и поэтому продолжает повторять инициализацию value снова и снова. Однако, возможно, Кланг находится в правильном положении, поскольку как текущая, так и подготовленная соответствующая фраза в [temp.point]/4 утверждают, что PoI перед ближайшим объявлением. То есть эта декларация не считается частью частичного экземпляра (пока). Kinda имеет смысл, если вы рассматриваете вышеупомянутый сценарий. Временное решение для GCC: используйте форму декларации, в которой имя не объявляется до тех пор, пока не инициализируется инициализатор.

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

Скомпилируется с помощью GCC.


& крестик; Обратите внимание, что в этом случае value может полагаться исключительно на ранее объявленные члены, то есть не может делегировать член, объявленный таким образом, что value находится в области видимости (тем самым обходя описанную выше проблему).