Что случилось с моими членами класса условного шаблона SFINAE redux:?

Я новичок в написании кода метапрограммирования шаблона (а не просто в его чтении). Так что я столкнулся с некоторыми проблемами нубов. Один из них довольно хорошо резюмирован в сообщении, не относящемся к SO, под названием " Что случилось с моим SFINAE? ", который я буду преобразовывать в C ++ 11 следующим образом:

(Примечание: в этом примере «мысленного эксперимента» я дал методам разные имена только для помощи в диагностике ошибок. См. @R .MartinhoЗаметки Фернандеса о том, почему вы на самом деле не выбрали бы этот подход на практике при отсутствии перегрузок.)

#include <type_traits>

using namespace std;

template <typename T>
struct Foo {
    typename enable_if<is_pointer<T>::value, void>::type
    valid_if_pointer(T) const { }

    typename disable_if<is_pointer<T>::value, void>::type
    valid_if_not_pointer(T) const { }
};

int main(int argc, char * argv[])
{
    int someInt = 1020;
    Foo<int*>().valid_if_pointer(&someInt);    
    Foo<int>().valid_if_not_pointer(304);

    return 0;
}

@Alf говорит, что с SFINAE произошло следующее: «Этого там не было вообще», и предлагает компилировать, но шаблонизирует функции вместо класса. Это может быть правильным для некоторых ситуаций, но не для всех. (Например: я специально пытаюсь написать контейнер, который может содержать типы, которые могут или не могут быть копируемыми, и мне нужно включать и выключать методы в зависимости от этого.)

В качестве обходного пути я попробовал это ... и, похоже, он работает правильно.

#include <type_traits>

using namespace std;

template <typename T>
struct FooPointerBase {
    void valid_if_pointer(T) const { }
};

template <typename T>
struct FooNonPointerBase {
    void valid_if_not_pointer(T) const { }
};

template <typename T>
struct Foo : public conditional<
    is_pointer<T>::value, 
    FooPointerBase<T>,
    FooNonPointerBase<T> >::type {
};

int main(int argc, char * argv[])
{
    int someInt = 1020;
#if DEMONSTRATE_ERROR_CASES
    Foo<int*>().valid_if_not_pointer(&someInt);
    Foo<int>().valid_if_pointer(304);
#else
    Foo<int*>().valid_if_pointer(&someInt);
    Foo<int>().valid_if_not_pointer(304);
#endif
    return 0;
}

Но если это не нарушено (не так ли?), это определенно не соответствует хорошей общей методологии включения и выключения методов в шаблонном классе на основе анализа типа на наличие признаков. Есть ли лучшее решение?


person HostileFork says dont trust SE    schedule 17.07.2012    source источник
comment
Это может быть правильным для некоторых ситуаций, но не для всех. Честно говоря, единственная ситуация, о которой я могу думать, - это когда вам нужен стабильный двоичный интерфейс. В любом случае нет, я не думаю, что есть лучшее решение.   -  person R. Martinho Fernandes    schedule 18.07.2012
comment
@ R.MartinhoFernandes Я в замешательстве ... как вы думаете, мало ли применимо для контейнера, который обнюхивает черты типа содержащегося в нем типа и имеет разные методы, включаемые и выключаемые в зависимости от этого? (Я бы процитировал boost :: optional, примененный к подвижному типу, поскольку я рассматриваю это и некоторые связанные с этим проблемы.) И вы говорите, что не думаете, что есть лучшее решение, чем то, что я придумали, или вы не думаете, что есть лучшее решение, чем Альф, для удаления шаблона из шаблона? : - /   -  person HostileFork says dont trust SE    schedule 18.07.2012
comment
@HostileFork Я не думаю, что есть решение лучше, чем создание шаблонов функций или ваше. Оба хороши, я предпочитаю создавать шаблоны функций (как это делала Flexo ниже), потому что в целом это меньше проблем (что? Пара новых классов для каждого критерия?). И да, ваш вариант использования прекрасен. Я просто думаю, что если вы не предоставите две перегрузки, в SFINAE нет необходимости. На самом деле, мне кажется, СФИНАЭ хуже. Дай мне немного, я думаю, это лучше подходит для ответа;)   -  person R. Martinho Fernandes    schedule 18.07.2012


Ответы (2)


Во-первых, C ++ 11 не поддерживает disable_if ускорения. Поэтому, если вы собираетесь перейти на ускоренный код, вам нужно будет использовать enable_if с отрицательным условием (или переопределить свою собственную конструкцию disable_if).

Во-вторых, для того, чтобы SFINAE могла проникнуть на уровень методов и применяться к ним, эти методы сами должны быть шаблонами. Тем не менее, ваши тесты должны выполняться по параметрам этих шаблонов ... поэтому код типа enable_if<is_pointer<T> не будет работать. Вы можете уточнить это, сделав некоторый аргумент шаблона (скажем, X) по умолчанию равным T, а затем вставив статическое утверждение о том, что вызывающий объект явно не специализировал его на чем-то еще.

Это означает, что вместо написания:

template <typename T>
struct Foo {
    typename enable_if<is_pointer<T>::value, void>::type
    valid_if_pointer(T) const { /* ... */ }

    typename disable_if<is_pointer<T>::value, void>::type
    valid_if_not_pointer(T) const { /* ... */ }
};

... вы бы написали:

template <typename T>
struct Foo {
    template <typename X=T>
    typename enable_if<is_pointer<X>::value, void>::type
    valid_if_pointer(T) const {
        static_assert(is_same<X,T>::value, "can't explicitly specialize");
        /* ... */
    }

    template <typename X=T>    
    typename enable_if<not is_pointer<X>::value, void>::type
    valid_if_not_pointer(T) const {
        static_assert(is_same<X,T>::value, "can't explicitly specialize");
        /* ... */
    }
};

Оба теперь являются шаблонами, и enable_if использует параметр шаблона X, а не T, который предназначен для всего класса. В частности, речь идет о подстановке, которая происходит при создании набора кандидатов для разрешения перегрузки - в вашей начальной версии подстановки шаблона во время разрешения перегрузки не происходит.

Обратите внимание, что статическое утверждение предназначено для сохранения намерения исходной проблемы и предотвращения того, чтобы кто-то мог компилировать такие вещи, как:

Foo<int>().valid_if_pointer<int*>(someInt);
person Flexo    schedule 17.07.2012
comment
Я пробовал именно это ... (ну, я сделал для них шаблоны и добавил фиктивные параметры, я не придумал трюк с X = T). Но у меня есть ошибки (gcc 4.7) в строках no type named ‘type’ in ‘struct std::enable_if<false, void>, и, используя ваш код, я получаю то же самое. Это действительно компилируется для вас? - person HostileFork says dont trust SE; 18.07.2012
comment
@Flexo Я решаю проблему с nullptr, так как это nullptr_t (ну!). Но даже убрав это из примера, я получаю ‘struct Foo<int>’ has no member named ‘valid_if_not_pointer’. Я использую g ++ (Debian 4.7.0-8) 4.7.0, может быть, мой компилятор недостаточно актуален? - person HostileFork says dont trust SE; 18.07.2012
comment
Ах, я не вставлял из ideone ... Я вставлял из приведенного выше кода, в котором используется disable_if< вместо enable_if<!. Может быть, вы сможете объяснить свой выбор в своем ответе? Это заставляет его работать! - person HostileFork says dont trust SE; 18.07.2012
comment
Господи, в этом и заключается проблема. Вещи, которые я пробовал и которые сработали бы, если бы я просто использовал enable_if, вызывали ошибки, которые я принял за проблемы с SFINAE, когда реальный источник проблемы был disable_if не определен, когда вы не используете boost! Это знание плюс template<typename X=T> - это ответ ... Я позволю вам исправить это и принять это! Спасибо! - person HostileFork says dont trust SE; 18.07.2012
comment
Сломано. X должен находиться в невыведенном контексте, иначе он может отличаться от T. Например, ваш код принимает Foo<int*> x; x.valid_if_not_pointer(0);. - person Johannes Schaub - litb; 19.07.2012
comment
@ JohannesSchaub-litb Я редактировал его; Вы можете подтвердить, что это то, что вы имели в виду? - person ecatmur; 19.07.2012
comment
@ecatmur спасибо. это тоже лучше, чем я имел в виду. Я думал о typename identity<X>::type, но это гораздо уродливее, чем просто сказать T :) - person Johannes Schaub - litb; 19.07.2012
comment
@ JohannesSchaub-litb Но если кто-то обеспокоен несоответствием типов, разве не стоит проверять совпадение типов? Нравится enable_if< is_same<X, T>::value and is_pointer<X>::value, void>::type? С тестом не имеет значения, используете ли вы X или T. Без него у вас могут быть случаи, когда вы просто завершите тестирование фиктивного типа ..._ 2_ - person HostileFork says dont trust SE; 19.07.2012
comment
@Flexo В целом звучит как хорошая идея. Но в этом конкретном сценарии valid_if_somecondition() методы действительно относятся к тесту на начальном T. X вводится только для того, чтобы предложить прокси, который можно использовать в тестировании enable_if. Таким образом, вы в значительной степени хотите отключить любую возможность явно передавать ему тип, который может (для некоторых условий) давать другой ответ ... - person HostileFork says dont trust SE; 19.07.2012
comment
На самом деле, static_assert( is_same<X, T>::value, "cannot explicitly specialize this method" ); в теле метода, вероятно, более уместен. - person HostileFork says dont trust SE; 19.07.2012
comment
@Flexo Надеюсь, вы не против, но я немного переварил комментарии к вашему ответу и немного переписал. Привычки из Википедии трудно умереть. Если вы хотите вернуть свой стиль / тон или любые другие изменения, конечно, не стесняйтесь. - person HostileFork says dont trust SE; 19.07.2012
comment
@HostileFork - редактирование выглядит хорошо, и я определенно не против, чтобы его редактировали - в этом весь смысл сайта! Я хочу, чтобы больше людей вносили больше правок, а не задавали вопросы и ответы по стилю. - person Flexo; 20.07.2012

На мой взгляд, вам не нужна СФИНАЭ здесь. SFINAE полезен для выбора между различными шаблонными перегрузками. По сути, вы используете его, чтобы помочь компилятору выбрать между template <typename Pointer> void f(Pointer); и template <typename NotPointer> void f(NotPointer);.

Это не то, что вам здесь нужно. Здесь у вас есть две функции с разными именами, а не две одинаковые перегрузки. Компилятор уже может выбирать между template <typename Pointer> void f(Pointer); и template <typename NotPointer> void g(NotPointer);.

Я приведу пример, чтобы объяснить, почему я считаю SFINAE не только ненужным, но и нежелательным.

Foo<int> not_pointer;
Foo<int*> pointer;

not_pointer.valid_if_pointer(); // #1
not_pointer.valid_if_not_pointer(); // #2
pointer.valid_if_pointer(); // #3
pointer.valid_if_not_pointer(); // #4

Теперь предположим, что вам удалось заставить это работать с SFINAE. Попытка скомпилировать этот фрагмент кода приведет к ошибкам в строках №1 и №4. Эти ошибки будут похожи на «член не найден» или что-то подобное. Он может даже указать функцию как отвергнутый кандидат в разрешении перегрузки.

Теперь предположим, что вы сделали это не с SFINAE, а с static_assert. Нравится:

template <typename T>
struct Foo {
    void valid_if_pointer(T) const {
        static_assert(std::is_pointer<T>::value, "valid_if_pointer only works for pointers");
        // blah blah implementation
    }

    void valid_if_not_pointer(T) const {
        static_assert(!std::is_pointer<T>::value, "valid_if_not_pointer only works for non-pointers");
        // blah blah implementation
    }
};

При этом вы получите ошибки в той же строке. Но вы получите очень короткие и полезные ошибки. Кое-что люди просят у разработчиков компиляторов годами. И теперь он у вас на пороге :)

Вы получаете то же самое: ошибки в обоих случаях, за исключением того, что вы получаете гораздо лучший вариант без SFINAE.

Также обратите внимание, что если вы вообще не использовали static_assert и реализация функций была действительна только при наличии указателей или не указателей, соответственно, вы все равно получили бы ошибки в соответствующих строках, за исключением, возможно, более неприятных.

TL; DR: если у вас нет двух фактических шаблонных функций с одинаковым именем, предпочтительно использовать static_assert вместо SFINAE.

person R. Martinho Fernandes    schedule 17.07.2012
comment
Я ценю вашу критику приложения, я в курсе этой части. Я изменил имена в целях тестирования, чтобы избежать путаницы при отладке механики. На самом деле оказалось, что единственная проблема, с которой я столкнулся с альтернативными вариантами, которые я пробовал, заключается в том, что в C ++ 11 нет disable_if, и я ошибочно принимал ошибки, которые я получал с моими попытками, как проблемы с SFINAE, хотя на самом деле они не были такими вещами, как disable_if проблемы! Но я рад, что научился template<typename X=T> трюку, кажется, это правильное решение. - person HostileFork says dont trust SE; 18.07.2012