Использует ли условный аргумент boost::enable_if_c‹› короткое замыкание?

Видите is >> i внизу моего кода? Я хочу, чтобы g++ (C++03; у меня есть причины) использовал первый шаблон operator>>() — тот, который печатает «неконтейнерный тип», потому что правое выражение — это int, а не, например, vector. Вместо этого он рассматривает последний — тот, который печатает «тип контейнера фиксированной длины». Из сообщений об ошибках я вижу, что он оценивает условный аргумент enable_if_c<> последнего шаблона, что приводит к всевозможным проблемам, потому что has_resize<T>::value делает вещи, которые не будут работать с контейнером.

Я бы подумал, что, поскольку первое подвыражение в условном параметре enable_if_c, is_container<C>::value, предположительно оценивается как false, второе подвыражение, has_resize<C>::value, не будет оцениваться. Либо оператор &&, разделяющий два выражения, не делает короткого замыкания, либо первое подвыражение необъяснимым образом оценивается как истинное для int. Любая идея, что это такое и что я могу с этим поделать? (Отладка TMP действительно сложна. Я хотел бы выполнять компиляцию пошагово, поскольку компилятор рассматривает каждый шаблон.)

Да, и если вы измените #if 1 на #if 0, будет использоваться альтернативный шаблон has_resize<T>, который работает как положено. Однако этот шаблон не так хорошо определяет, можно ли изменить размер типа, что я и пытаюсь сделать. Тот, который я пытаюсь заставить работать, тоже не идеален, но он лучше.

Если вы хотите поиграть с кодом, он также доступен на Wandbox. (оболочка C++ тоже. Я играюсь с онлайн-компиляторами. Я сделал их список.)

#include <iostream>
#include <boost/spirit/home/support/container.hpp>

#if 1
// has_resize<T>::value is whether the (presumably) container class contains resize.
template<class T>
class has_resize
{
    struct Fallback { int resize; };
    struct Derived : T, Fallback { };

    template<class C, C>
    class check;

    typedef uint8_t no;
    typedef uint16_t yes;

    template<typename C> static no test(check<int Fallback::*, &C::resize> *);
    template<typename C> static yes test(...);

public:
    static const bool value = sizeof test<Derived>(0) == sizeof(yes);
};
#else
// has_resize<T>::value is whether the (presumably) container class contains allocator_type.
template <class T>
class has_resize
{
    typedef uint8_t yes;
    typedef uint16_t no;

    template <typename C> static yes test(class C::allocator_type *);
    template <typename C> static no test(...);

public:
    static const bool value = sizeof test<T>(0) == sizeof(yes);
};
#endif

class xstream { }; // For this example, the class doesn't need to do anything.

template <typename T>
typename boost::enable_if_c<
    !boost::spirit::traits::is_container<T>::value,
    xstream &>::type
    operator>>(xstream &ibs, T &b)
{
    std::cout << "non-container type" << std::endl;
    return ibs;
}

template <typename C>
typename boost::enable_if_c<
    boost::spirit::traits::is_container<C>::value && has_resize<C>::value,
    xstream &
>::type
operator>>(xstream &ibs, C &c)
{
    std::cout << "variable-length container type" << std::endl;
    ibs >> *c.begin();
    return ibs;
}

template <typename C>
typename boost::enable_if_c<
    boost::spirit::traits::is_container<C>::value && !has_resize<C>::value,
    xstream &
>::type
operator>>(xstream &ibs, C &c)
{
    std::cout << "fixed-length container type" << std::endl;
    ibs >> *c.begin();
    return ibs;
}

int main()
{
    int i;
    xstream is;
    is >> i;
}

ОБНОВЛЕНИЕ: вот код с исправлением, предложенным @Jarod42:

#include <iostream>
#include <vector>
#include <set>
#if __cplusplus > 199711L
#include <array>
#endif
#include <boost/spirit/home/support/container.hpp>

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               \
    template <typename U>                                                   \
    class traitsName                                                        \
    {                                                                       \
    private:                                                                \
        template<typename T, T> struct helper;                              \
        template<typename T>                                                \
        static char check(helper<signature, &funcName>*);                   \
        template<typename T> static int check(...);                         \
    public:                                                                 \
        static                                                              \
        const bool value = sizeof(check<U>(0)) == sizeof(char);             \
    }

#if __cplusplus > 199711L
DEFINE_HAS_SIGNATURE(has_resize, T::resize, void (T::*)(typename T::size_type));
#else
DEFINE_HAS_SIGNATURE(has_resize, T::resize, void (T::*)(typename T::size_type, typename T::value_type));
#endif

class xstream { }; // For this example, the class doesn't need to do anything.

template <typename T>
typename boost::enable_if_c<
    !boost::spirit::traits::is_container<T>::value,
    xstream &>::type
    operator>>(xstream &ibs, T &b)
{
    std::cout << "non-container type" << std::endl;
    return ibs;
}

template <typename C>
typename boost::enable_if_c<
    boost::spirit::traits::is_container<C>::value && has_resize<C>::value,
    xstream &
>::type
operator>>(xstream &ibs, C &c)
{
    std::cout << "variable-length container type" << std::endl;
    ibs >> *c.begin();
    return ibs;
}

template <typename C>
typename boost::enable_if_c<
    boost::spirit::traits::is_container<C>::value && !has_resize<C>::value,
    xstream &
>::type
operator>>(xstream &ibs, C &c)
{
    std::cout << "fixed-length container type" << std::endl;
    ibs >> *c.begin();
    return ibs;
}

int main()
{
    int i;
    std::vector<int> vi;
    std::set<int> si;
#if __cplusplus > 199711L
    std::array<int, 1> ai;
#endif
    xstream xs;
    xs >> i >> vi >> si;
#if __cplusplus > 199711L
    xs >> ai;
#endif
}

person plong    schedule 25.06.2015    source источник


Ответы (1)


В вашем случае у вас есть серьезная ошибка в struct Derived : T, Fallback { }; с T = int ::value вызывает создание экземпляра класса.

Я использую следующее:

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               \
    template <typename U>                                                   \
    class traitsName                                                        \
    {                                                                       \
    private:                                                                \
        template<typename T, T> struct helper;                              \
        template<typename T>                                                \
        static std::uint8_t check(helper<signature, &funcName>*);           \
        template<typename T> static std::uint16_t check(...);               \
    public:                                                                 \
        static                                                              \
        constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
    }

DEFINE_HAS_SIGNATURE(has_resize, T::foo, int (T::*));

Живое демо

person Jarod42    schedule 25.06.2015
comment
Спасибо за внимание, но я уже умею это делать на современном C++. Мне нужно решение C++03. - person plong; 25.06.2015
comment
@user4438540: работает с C++03, если изменить constexpr и std::uintx_t Демо - person Jarod42; 25.06.2015
comment
Хорошо, мы подошли очень близко... Это работает для различения контейнерных и неконтейнерных типов, таких как vector и int. Однако он никогда не находит функцию-член resize() в типах контейнеров, в которых она действительно есть. Я даже подправил подпись с твоего int (T::*) на void (T::*)(std::size_t) и даже void (T::*)(std::size_t, T::value_type), но это не помогает. В следующей демонстрации он определяет как vector, так и set как не имеющие функцию-член resize(). Демо - person plong; 25.06.2015
comment
Ура, я решил свою проблему. Мне нужно было typename перед каждым параметром в подписи. Поскольку C++11 изменил прототип resize(), мне также понадобились две версии подписи. - person plong; 26.06.2015