// Culled by SFINAE if reserve does not exist or is not accessibletemplate <typename T>
constexpr auto has_reserve_method(T& t) -> decltype(t.reserve(0), bool()) {
return true;
}
// Used as fallback when SFINAE culls the template method
constexpr bool has_reserve_method(...) { return false; }
Note: thanks to the semantics of , (the comma operator) you can chain multiple expressions in decltype and only the last actually decides the type. Handy to check multiple operations.
Note: thanks to the semantics of , (the comma operator) you can chain multiple expressions in decltype and only the last actually decides the type. Handy to check multiple operations.
Пока какой-нибудь негодяй не перегрузит запятую для своего типа.
К счастью, против этого есть контрмера: void. Он всегда апеллирует к определённой компилятором запятой.
Здравствуйте, Кодт, Вы писали:
К>К счастью, против этого есть контрмера: void. Он всегда апеллирует к определённой компилятором запятой.
Только вместо
К>decltype(foo(t), void(0), true)
или
decltype(foo(t), void(), true)
вполне достаточно писать
decltype((void)foo(t), true)
или
decltype(static_cast<void>(foo(t)), true)
Несмотря на потенциальную возможность существования у типа decltype(foo(t)) оператора operator void() неявного приведения к void, по стандарту он никогда не должен быть вызван:
[class.conv.fct] 12.3.2/1
A conversion function is never used to convert a (possibly cv-qualified) object to the (possibly cv-qualified) same object type (or a reference to it), to a (possibly cv-qualified) base class of that type (or a reference to it), or to (possibly cv-qualified) void.
И далее в тексте сноски:
These conversions are considered as standard conversions for the purposes of overload resolution (13.3.3.1, 13.3.3.1.4) and therefore initialization (8.5) and explicit casts (5.2.9). A conversion to void does not invoke any conversion function (5.2.9). Even though never directly called to perform a conversion, such conversion functions can be declared and can potentially be reached through a call to a virtual conversion function in a base class.
Какой-то ужасно хрупкий код. Стоит разнести проверяемый тип (typename T::type::type) и ломающий компиляцию возвращаемый (A<void>) — и компилятор весело забивает на перегрузку void,X
Попробовал сделать комбинаторный взрыв и получить минимальный пример
Что характерно, неполный тип — Yield<void> или Combo<???,void> — нигде не используется, кроме как будучи формальным параметром шаблона Arg.
При этом void(),YM<void>() прокатило, а void(),YYi<void>() нет.
Суть перебора: участвует ли неполный тип
— в выводе зависимого типа (валится!)
— в роли другого аргумента шаблона Arg
— — в выводе зависимого типа используется такой же шаблон (валится!!!) — т.е. Yield,Yield или Combo,Combo
— — в выводе зависимого типа используется другой шаблон — т.е. Yield<void>,Match<void> и т.п.
Пример опирался на тот факт, что любые ошибки, возникающие в процессе инстанцирования определения какого-нибудь шаблона, делают всю программу невалидной, так как к ним SFINAE неприменимо. Попытка определить переменную неполного типа — это только один из множества возможных способов получить подобного рода ошибку.
template <class T>
struct S
{
typedef T type;
typedef typename T::type nested_type;
};
void f(int) {}
template <class T>
typename S<T>::type f(T) {}
int main()
{
f(0);
}
Здесь у int пытаются получить вложенный тип type, которого там конечно же нет. Припрятать такие грабли в запятой довольно легко
template <class T>
struct S
{
typedef T type;
typedef typename T::type nested_type;
};
struct A {};
template <class T>
typename S<T*>::type operator,(A, T) {}
int main()
{
A(), void();
}
Суть та же, что и в предыдущем примере, только здесь void* вместо int.
Здравствуйте, Ku-ku, Вы писали:
KK>Пример опирался на тот факт, что любые ошибки, возникающие в процессе инстанцирования определения какого-нибудь шаблона, делают всю программу невалидной, так как к ним SFINAE неприменимо. Попытка определить переменную неполного типа — это только один из множества возможных способов получить подобного рода ошибку.
Ненене. Вот, например, shared_ptr можно параметризовать неполным типом (предобъявленным), и это не делает программу невалидной; либо имеет место наглая эксплуатация дефекта стандарта?
В моём коде нигде нет обращения к тем свойствам неполного типа, которые могли бы что-то сломать. Ни экземпляр его пытаюсь создать, ни даже размер спросить. Ни даже указатель получить!
Только выковыриваю зависимое имя типа, которое там со всей очевидностью присутствует.
Причём компилятор — ну ладно бы ругался на выковыривание зависимого типа из неполного типа; так нет же, он ругается тогда, когда зависимый тип выковыривается из полноценного типа — полученного из того же шаблона, но с другими параметрами.
Это вообще цирк какой-то.
Здравствуйте, Кодт, Вы писали:
К>В моём коде нигде нет обращения к тем свойствам неполного типа, которые могли бы что-то сломать.
Речь про эти закомментированные строчки?
void(), YY<void>();
void(), YYi<void>();
Там у Yield<void> запрашивают вложенный тип type, что приводит к необходимости инстанцировать определение Yield<void>, которое невалидно из-за наличия в нём поля типа void.