Является ли следующий код специализации шаблона нестандартным или ошибка в VS-C ++? - PullRequest
8 голосов
/ 12 марта 2012

Следующий код компилируется в GCC (я использовал ideone, который использует gcc-4.3.4), но не компилируется в Visual Studio.Это стандартный код и ошибка в Visual C ++ 2008 и 2010 (я пробовал в обоих) или нестандартный, и GCC с радостью его скомпилирует?

namespace cool
{
  template <bool, typename = void> struct enable_if {};
  template <typename T> struct enable_if<true, T> { typedef T type; };

  template <typename T0, typename T1> struct is_same { enum { value = false }; };
  template <typename T> struct is_same<T, T> { enum { value = true }; };
}

struct BasePolicy {};
struct BasePolicy2 {};
struct Faz {};

template <typename Policy,
typename = typename cool::enable_if<cool::is_same<BasePolicy, Policy>::value || cool::is_same<BasePolicy2, Policy>::value>::type >
struct Foo;

template <typename Policy>
struct Foo<Policy> {
  Foo();
};

template <typename Policy>
Foo<Policy>::Foo() {
}

int main()
{
  Foo<BasePolicy2> fb;
  // Foo<Faz> fb1;
}

Ошибка 1, ошибка C2039: '{ctor} ': не является членом' Foo 'main.cpp 25

Обратите внимание, что проблема заключается в неконструктивном определении конструктора Foo.Если вы определите его в классе, то Visual-C ++ будет счастлив:

template <typename Policy>
struct Foo<Policy> {
  Foo() {}
};

Кроме того, следующий код компилируется в обоих (обратите внимание, что || и логика после него отсутствует):

namespace cool
{
  template <bool, typename = void> struct enable_if {};
  template <typename T> struct enable_if<true, T> { typedef T type; };

  template <typename T0, typename T1> struct is_same { enum { value = false }; };
  template <typename T> struct is_same<T, T> { enum { value = true }; };
}

struct BasePolicy {};
struct BasePolicy2 {};
struct Faz {};

template <typename Policy,
  typename = typename cool::enable_if<cool::is_same<BasePolicy, Policy>::value>::type >
struct Foo;

template <typename Policy>
struct Foo<Policy> {
  Foo();
};

template <typename Policy>
Foo<Policy>::Foo() {
}

int main()
{
  Foo<BasePolicy> fb;
  // Foo<Faz> fb1;
}

Кредит, при котором требуется кредит, это слегка измененная версия, данная мне Дитмаром Кюлем)

Ответы [ 3 ]

1 голос
/ 30 апреля 2012

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 доволен этим тоже.

0 голосов
/ 12 марта 2012

О. Не должно ли это быть:

template <typename Policy>
struct Foo {
  Foo();
};

template <typename Policy>
Foo<Policy>::Foo() {
}

(Без <Policy> в объявлении структуры.) Не уверен, что говорится в стандарте, но объявление структуры шаблона с параметром шаблона после имени структуры должно означать частичную специализацию этого шаблона.

0 голосов
/ 12 марта 2012

Мне кажется, это связано с этой ошибкой компилятора.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...