Использование std::enable_if для возвращаемого типа функции шаблона для использования SFINAE — ошибка компиляции

Следующий код

#include <type_traits>

struct CByteArray {};
struct HLVariant {
    HLVariant() {}
    HLVariant(const HLVariant&) {}
    HLVariant(const CByteArray&) {}

    };

template <typename T>
inline typename std::enable_if<!std::is_pod<T>::value, CByteArray>::type serialize(const T& value)
{
    return serialize(HLVariant(value));
}

template <typename T>
inline typename std::enable_if<std::is_pod<T>::value, CByteArray>::type serialize(const T& value)
{
    return CByteArray();
}

template <>
inline CByteArray serialize(const HLVariant& value)
{
    return CByteArray();
}

int main()
{
    serialize(0);
    serialize(CByteArray());
    serialize(HLVariant());

    return 0;
}

вызывает ошибку компиляции C2794: 'type' : is not a member of any direct or indirect base class of 'std::enable_if<false,CByteArray>' в MSVC 2013. Однако это работает в ideone: введите здесь описание ссылки

В чем здесь ошибка?

Ошибка одинакова в MSVC 2010, 2012 и 2013.


person Violet Giraffe    schedule 03.11.2014    source источник
comment
Я бы поставил на ошибку MSVC 2013 :)   -  person Pradhan    schedule 03.11.2014
comment
@Pradhan: если вы подразумеваете, что компилятор MS в чем-то уступает GCC, то вы очень далеки от истины.   -  person Violet Giraffe    schedule 03.11.2014
comment
@VioletGiraffe В соответствии с языком C ++? Компилятор MS сильно отстает. У него есть и другие преимущества, но не обманывайте себя насчет соответствия языку C++. Какое обновление MSVC 2013?   -  person Yakk - Adam Nevraumont    schedule 03.11.2014
comment
отлично работает в VS14 CTP   -  person Piotr Skotnicki    schedule 03.11.2014
comment
@Yakk: Хорошо, он еще не совместим с С++ 14. В любом случае, мы можем вернуться к теме, пожалуйста?   -  person Violet Giraffe    schedule 03.11.2014
comment
Нет, он не совместим с С++ 11. Было бы преувеличением сказать, что он совместим с C++03. Итак, какое обновление MSVC2013 вы используете? (GM? U1? U2? U3? -- они разные) На какой именно строке возникает ошибка? Можете ли вы удалить код, который не предотвращает ошибку — она должна возникать только с одной перегрузкой serialize, что упростит определение того, что происходит не так. Если вы удалите serialize, на котором нет ошибки, будет ли она по-прежнему генерировать ошибку? Если это так, отредактируйте свой пост с более простым кодом.   -  person Yakk - Adam Nevraumont    schedule 03.11.2014
comment
@Yakk: это происходит с MSVC 2010, 2012 и 2013, поэтому не имеет значения, какое у меня обновление (обновление 3, если вы все еще считаете его актуальным). Я думаю, что я ничего не мог бы устранить, не удаляя соответствующий код. Согласно сообщению об ошибке, ошибка возникает в строке 27.   -  person Violet Giraffe    schedule 03.11.2014
comment
@Yakk: достаточно честно, позвольте мне попробовать. Я все еще не уверен, что это эквивалентное преобразование, поскольку SFINAE больше не будет использоваться для разрешения шаблонов.   -  person Violet Giraffe    schedule 03.11.2014
comment
SFINAE происходит до того, как стандарт выбирает перегрузки. Глядя ниже, в проблему может быть вовлечен template<>, что может быть побочным эффектом того, как MSVC выполняет полную специализацию. Какая строка является строкой 27?   -  person Yakk - Adam Nevraumont    schedule 03.11.2014
comment
@Yakk: строка 27 — это закрывающая скобка тела template <> CByteArray serialize(const HLVariant& value). Что бы это ни значило.   -  person Violet Giraffe    schedule 03.11.2014
comment
А, кажется, я понял. MSVC рассматривает это как полную специализацию... обоих? ... из приведенных выше шаблонов и по какой-то причине выполняет замену обратно в оригинал и получает ошибку, которую он считает серьезной. Как правило, не стоит специализировать шаблонные функции, специализация шаблонов функций выполняется плохо.   -  person Yakk - Adam Nevraumont    schedule 03.11.2014
comment
@Yakk: Я экспериментировал с ним и могу точно сказать две вещи: 1. ошибка возникает при любой компиляции кода шаблона, а не только при создании экземпляра. т. е. если я удалю все вызовы serialize, ошибка останется.   -  person Violet Giraffe    schedule 03.11.2014
comment
@Yakk: 2. Компилятор пытается скомпилировать (или, может быть, просто разобрать, я не знаю) каждую специализацию и каждую базовую неспециализированную версию, он не понимает, что я пытаюсь их скрыть. Другими словами: когда компилятор встречает специализацию <> serialize (const int&), он затем пытается подставить int в обе базовые версии шаблона, а поскольку одна из них всегда скрыта, компиляция 1 из bae-функций всегда будет завершаться ошибкой независимо от типа после того, как компилятор встречает enable_if<false, T>. Ошибка компилятора?   -  person Violet Giraffe    schedule 03.11.2014
comment
Я предполагаю, что он пытается выяснить, какой шаблонной функцией является ваша специализация, и делает это с помощью подстановки и генерирует серьезные ошибки. Не специализируйте шаблоны функций, это почти всегда плохая идея, даже если здесь не было ошибки MSVC.   -  person Yakk - Adam Nevraumont    schedule 03.11.2014
comment
@Yakk: как так? Почему плохая идея, я имею в виду? Поможет ли мне превратить мои бесплатные шаблоны функций в шаблоны функций-членов класса? Или шаблоны классов?   -  person Violet Giraffe    schedule 03.11.2014
comment
Потому что специализация странно реагирует на перегрузки и часто делает не то, что, по вашему мнению, должна делать. Специализируйте классы, перегружайте функции.   -  person Yakk - Adam Nevraumont    schedule 03.11.2014
comment
@Yakk: В этом случае мне нужен шаблон, поэтому о перегрузке не может быть и речи. Итак, вы говорите, что я должен заключить функцию serialize в шаблон класса и специализировать этот шаблон? Как насчет специализации шаблонного метода класса, не являющегося шаблоном?   -  person Violet Giraffe    schedule 03.11.2014
comment
Вы уже используете перегрузки — у вас есть две шаблонные функции, которые перегружают друг друга. Они НЕ являются специализациями друг друга. Если вы думаете, что вам нужна одна функция шаблона, вы уже не справились со своей задачей. Шаблонные функции могут нормально перегружать друг друга, а нешаблонные функции могут вместе с ними участвовать в разрешении перегрузок (с осторожностью).   -  person Yakk - Adam Nevraumont    schedule 03.11.2014
comment
@Yakk: Как это перегружается, если можно скомпилировать только 1 из 2 вариантов функции? И у них точно такая же подпись.   -  person Violet Giraffe    schedule 03.11.2014


Ответы (1)


Мне кажется, это нормально, но я могу это исправить, удалив template<> из финальной перегрузки serialize. Не нужно делать это полной специализацией, когда подойдет обычная перегрузка!

РЕДАКТИРОВАТЬ: Что еще работает, так это предоставление специализации шаблона, которая соответствует только HLVariant (и дальнейшее ограничение других специализаций, чтобы они больше не соответствовали HLVariant, чтобы избежать двусмысленности).

Это должно сделать это:

http://ideone.com/0UGkcn

#include <type_traits>
#include <iostream>

struct CByteArray {};
struct NonPod {public: int a; private: int b;};
struct HLVariant {
    HLVariant() {}
    HLVariant(const HLVariant&) {}
    HLVariant(const CByteArray&) {}
    HLVariant(const NonPod&) {}
};

template <typename T>
inline typename std::enable_if<std::is_same<T, HLVariant>::value && !std::is_pod<T>::value, CByteArray>::type serialize(const T& value)
{
    std::cout << "serialize non-pod variant\n";
    return CByteArray();
}

template <typename T>
inline typename std::enable_if<!std::is_same<T, HLVariant>::value && !std::is_pod<T>::value, CByteArray>::type serialize(const T& value)
{
    std::cout << "serialize non-pod non-variant\n";
    return serialize(HLVariant(value));
}

template <typename T>
inline typename std::enable_if<std::is_pod<T>::value, CByteArray>::type serialize(const T& value)
{
    std::cout << "serialize pod\n";
    return CByteArray();
}

int main()
{
    std::cout << "int:\n";
    serialize(0);
    std::cout << "CByteArray:\n";
    serialize(CByteArray());
    std::cout << "HLVariant:\n";
    serialize(HLVariant());
    std::cout << "NonPod:\n";
    serialize(NonPod());
}
person Ben Hymers    schedule 03.11.2014
comment
Я вижу, что вы там сделали ... Проблема в том, что в моем реальном коде такая перегрузка вызовет целую кучу ошибок, поскольку перегрузки без шаблонов имеют приоритет (IIRC), а HLvariant может быть построен примерно из 50 различных типов через не -явные конструкторы. Другими словами, будут вызваны неправильные версии serialize. - person Violet Giraffe; 03.11.2014
comment
Определенное пользователем преобразование должно быть менее хорошим соответствием, чем точное соответствие шаблона? - person Yakk - Adam Nevraumont; 03.11.2014
comment
Хорошо, похоже, вам нужна специализация шаблона, которая соответствует только HLVariant (и чтобы другие специализации не соответствовали HLVariant, чтобы избежать двусмысленности). Я обновлю... - person Ben Hymers; 03.11.2014
comment
@BenHymers: да, это точно. И я добавлю больше специализаций в будущем. - person Violet Giraffe; 03.11.2014
comment
Итак, специализация не работает, но скрытие с помощью enable_if все еще работает? Я провел короткую проверку - компилируется в MSVC. - person Violet Giraffe; 03.11.2014
comment
Да, я считаю, что ваш исходный код должен быть принят, и то, что он не скомпилируется в MSVC, является ошибкой; это всего лишь обходной путь, который, к сожалению, довольно громоздкий... - person Ben Hymers; 03.11.2014