Visual C ++ 2008/2010 виноват.Но это можно обойти - несколькими способами.
Давайте рассмотрим тип Foo<BasePolicy2> fb
.
. В этом объявлении по умолчанию указан второй параметр шаблона Foo <>, объявленный первым.Так что явно его тип:
/*1*/ Foo<BasePolicy2,cool::enable_if<
cool::is_same<BasePolicy, BasePolicy2>::value ||
cool::is_same<BasePolicy2,BasePolicy2>::value
>::type
>
Если вы уже уверены, что /*1*/
сводится к:
/*2*/ Foo<BasePolicy2,void>
, тогда вы можете встретиться с нами снова на Рандеву , ниже.
Итак, мы видим, что тип:
/*3/ cool::enable_if<
cool::is_same<BasePolicy, BasePolicy2>::value ||
cool::is_same<BasePolicy2,BasePolicy2>::value
>
разрешается в:
/*4/ cool::enable_if<some_boolean_consant>
Далее, давайте проверим, как template enable_if<>
определено.В namespace cool
у нас есть:
/*5/ template <bool, typename = void> struct enable_if {};
/*6/ template <typename T> struct enable_if<true, T> { typedef T type; };
, поэтому /*4*/
в свою очередь по умолчанию использует второй параметр шаблона template enable_if<>
, а тип этого значения по умолчанию - void
.
Хорошо, тогда /*6*/
специализируется template enable_if<>
относительно его второго параметра шаблона всякий раз, когда его первый параметр bool имеет значение true
, и в этом случае говорится, что enable_if<>
должен экспортировать typedef type
, которыйимеет тип 2-го параметра шаблона.Если первый параметр bool равен false
, то этот typedef просто не будет существовать, и наш компилятор будет barf.
Хорошо, мы знаем, что если /*4*/
будет компилироваться вообще, то some_boolean_consant == true
, иэкспортированный тип type
соответствует типу по умолчанию второго параметра шаблона enable_if<>
.Что void
.
Теперь вывели тип:
/*7*/ cool::enable_if<
cool::is_same<BasePolicy, BasePolicy2>::value ||
cool::is_same<BasePolicy2,BasePolicy2>::value
>::type
Это void
.Итак, /*1*/
сводится к /*2*/
, и это тип по умолчанию Foo<BasePolicy2>
.
Что и должно быть, но если вы сомневаетесь в этом заключении, то просто добавьте это кпрограмма в глобальном масштабе и скомпилировать:
typedef cool::enable_if<
cool::is_same<BasePolicy, BasePolicy2>::value ||
cool::is_same<BasePolicy2,BasePolicy2>::value
>::type WhatType;
WhatType what_am_i;
Компилятор скажет:
'what_am_i' : illegal use of type 'void'
или слова на этот счет.
Свидание
Знание о том, что /*1/
= /*2/
дает нам некоторое преимущество в ошибке компиляции Visual C ++, о которой идет речь:
Error 1 error C2039: '{ctor}' : is not a member of 'Foo' main.cpp 25
Ошибка - жалоба на то, что конструктор, вызвавший ее, не является на самом делеконструктор, объявленный типом, которому он должен принадлежать, то есть, что Foo<Policy>::Foo()
не является конструктором Foo<Policy>
.
Теперь определение Foo<Policy>
по умолчанию задает второй параметр шаблона своего первоначального объявления, которое мызнать должно быть void
.Таким образом, возникает вопрос: действительно ли компилятор соблюдает этот по умолчанию второй параметр шаблона?- т.е. признает ли он, что определение шаблона Foo<Policy>
является определением шаблона Foo<Policy,void>
?
Ответ - Нет. Ведь если мы просто изменим определение шаблона и его конструктор, чтобы явно указать2-й параметр по умолчанию:
template <typename Policy>
struct Foo<Policy,void> {
Foo();
};
template <typename Policy>
Foo<Policy,void>::Foo() {
}
, после чего программа компилируется с использованием Visual C ++.
Возможно ли, что Visual C ++ будет придерживаться своих собственных (спорных) убеждений относительно стандарта C ++, вызывая эту ошибку?Или это просто сломано?Это просто сломано.Потому что, если мы попробуем это на этой более простой, но по существу той же программе, у нее не возникнет проблем:
/* Simple Case */
template<typename X, typename Y = void>
struct A;
template<typename X>
struct A<X> {
A();
};
template<typename X>
A<X>::A(){};
int main()
{
A<int> aint;
return 0;
}
Это показывает, что именно глоток шаблонного метапрограммирования /*3*/
вызывает расстройство желудка, и как указывает вопросующийесли экспериментально упростить этот глоток, удалив операцию ||
, все будет хорошо (за исключением того, что, конечно, наша логика cool::
не работает).
Временные решения?
Мыуже видел один.Просто сделайте параметр шаблона void
явным в определениях Foo<Policy>
и Foo<Folicy>::Foo()
.Вы можете уйти сейчас, если это все, что вы хотите знать.
Но это чешется.Мы применяем исправление на уровне /* Simple Case */
, когда мы знаем, что ошибка не является общей на этом уровне.Это не совсем понятно из работ метапрограммирования шаблонов компилятора, поэтому обходной путь, который не испытывает зуда, по крайней мере, ограничится namespace cool
.
Вот правило предостережения относительно шаблонов вспомогательного метапрограммирования (TMP), которое, я надеюсь, скоро будет забыто прогрессом компиляторов: Не позволяйте биглайтерам создавать экземпляры в полете .Такие шаблоны существуют толькочтобы облегчить логику компиляции. Пока компилятор может выполнять эту логику только путем рекурсивного определения
типы он может оставаться в своей зоне комфорта (по крайней мере, до тех пор, пока его глубина создания сохраняется). Если
он обязан создавать промежуточные типы для развития логики времени компиляции
peccadilloes склонны к поверхности.
Если вы кодируете свои вспомогательные классы TMP таким образом, что логика времени компиляции может быть достигнута только путем взятия значений
статических или перечисляемых констант, которые они выставляют, тогда вы заставляете компилятор все время создавать экземпляры,
потому что только так эти статические или перечисляемые константы приобретают значения.
Показательный пример, класс /*3*/
с его таинственно токсичной ||
операцией. Вспомогательные классы TMP namespace cool
делают свои
мета-логика логических констант - без необходимости.
Осторожный подход к вспомогательным классам TMP состоит в том, чтобы определить их так, чтобы все логические операции могли быть смоделированы, без создания экземпляров,
с помощью рекурсивных определений типов с экспортируемыми константами - чтобы они вам в конечном счете не понадобились - только когда все время компиляции
логика успешно приземлилась. Осторожная перезапись содержимого namespace cool
может выглядеть примерно так:
namespace cool
{
template<bool val>
struct truth_type
{
static const bool value = false;
};
template<>
struct truth_type<true>
{
static const bool value = true;
};
typedef truth_type<true> true_type;
typedef truth_type<false> false_type;
template<class lhs,class rhs>
struct or_type
{
typedef false_type type;
};
template<class lhs>
struct or_type<lhs,true_type>
{
typedef true_type type;
};
template<class rhs>
struct or_type<true_type,rhs>
{
typedef true_type type;
};
template <typename T, typename = void> struct enable_if {};
template <typename T> struct enable_if<true_type, T> { typedef T type; };
template <typename T0, typename T1> struct is_same {
typedef false_type type;
};
template <typename T> struct is_same<T, T> {
typedef true_type type;
};
}
и соответствующее объявление шаблона Foo <> будет выглядеть так:
template <typename Policy,
typename = typename cool::enable_if<
typename cool::or_type<
typename cool::is_same<BasePolicy, Policy>::type,
typename cool::is_same<BasePolicy2, Policy>::type
>::type
>::type>
struct Foo;
Таким образом, ни одна из наших cool::
вещей никогда не будет реализована, пока весь Шебанг не станет
конкретизирован: мы имеем дело только с типами для всей мета-логики. Если эти изменения
внесены в программу, то зуд обходной путь не требуется.
И GCC доволен этим тоже.