Ответ 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
находится в области видимости (тем самым обходя описанную выше проблему).