Как использовать операторы сравнения для варианта с содержащимися типами? - PullRequest
4 голосов
/ 06 февраля 2012

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

Например:

if(equals<int>(aVariant, 0)){
    //Something
} else {
    //Something else
}

с этой простой функцией шаблона, которую я написал для этой цели:

template<typename V, typename T>
inline bool equals(V& variant, T value){
    return boost::get<T>(&variant) && boost::get<T>(variant) == value;
}

Это хорошо работает, но код становится трудно читать. Я предпочитаю использовать операторы сравнения так:

if(aVariant == 0){
    //Something
} else {
    //Something else
}

Но я не смог прийти с действительной реализацией оператора. Проблема в том, что оператор == уже был реализован в варианте, который завершается с ошибкой во время компиляции ...

Кто-нибудь знает, как это реализовать? Или способ отключить это ограничение? Даже если мне придется реализовать версию для каждого возможного типа, содержащегося в варианте, это не проблема.

Спасибо

1 Ответ

8 голосов
/ 06 февраля 2012

Как прокомментировано, я думаю, что самым чистым способом решения этой головоломки было бы расширение реализации boost::variant<> с помощью политики оператора (на каждого оператора, действительно), которая позволяет клиентам переопределять поведение для внешнего использования. , ( Очевидно, что это много общего программирования ).

I реализовали обходной путь. Это позволяет реализовать пользовательские операторы для вариантов , даже если они реализованы в boost/variant.hpp.

Моя мозговая волна должна была использовать BOOST_STRONG_TYPEDEF.

Идея состоит в том, чтобы нарушить разрешение перегрузки (или, по крайней мере, сделать наши собственные перегрузки предпочтительным разрешением), сделав наши варианты другого фактического типа (это немного напоминает «отчаянный» барьер ADL: вы не можете un- using видимых имен из области и не можете перейти в «демилитаризованное пространство имен» (барьер), поскольку конфликтующие объявления находятся в в самом пространстве имен класса ; но вы можете сделать их не применять к вашему типу "приманки".

Увы, это не очень хорошо работает для operator< и семейства, потому что boost strong-typedef на самом деле много работает для сохранения (слабой) семантики общего порядка с типом 'base'. В обычном английском языке: сильные определения типов также определяют operator< ( делегирование реализации базового типа ).

Не волнуйтесь, мы можем сделать CUSTOM_STRONG_TYPEDEF и быть на нашем веселом пути. Посмотрите контрольные примеры в основном для подтверждения концепции (вывод ниже).

Из-за описанных интересных взаимодействий я выбрал operator< для этой демонстрации, но я полагаю, что у вас не будет ничего, чтобы настроить пользовательский operator== для ваших типов вариантов.

#include <boost/variant.hpp>
#include <boost/lexical_cast.hpp>
#include <string>
#include <iostream>

/////////////////////////////////////////////////////
// copied and reduced from boost/strong_typedef.hpp
#define CUSTOM_STRONG_TYPEDEF(T, D)                                 \
struct D                                                            \
    /*: boost::totally_ordered1< D           */                     \
    /*, boost::totally_ordered2< D, T        */                     \
    /*> >                                    */                     \
{                                                                   \
    T t;                                                            \
    explicit D(const T t_) : t(t_) {};                              \
    D(){};                                                          \
    D(const D & t_) : t(t_.t){}                                     \
    D & operator=(const D & rhs) { t = rhs.t; return *this;}        \
    D & operator=(const T & rhs) { t = rhs; return *this;}          \
    operator const T & () const {return t; }                        \
    operator T & () { return t; }                                   \
    /*bool operator==(const D & rhs) const { return t == rhs.t; } */\
    /*bool operator<(const D & rhs) const { return t < rhs.t; }   */\
};

namespace detail
{
    typedef boost::variant<unsigned int, std::string> variant_t;

    struct less_visitor : boost::static_visitor<bool>
    {
        bool operator()(const std::string& a, int b) const
        { return boost::lexical_cast<int>(a) < b; }

        bool operator()(int a, const std::string& b) const
        { return a < boost::lexical_cast<int>(b); }

        template <typename T>
            bool operator()(const T& a, const T& b) const
            { return a < b; }
    };

    struct variant_less
    {
        less_visitor _helper;

        bool operator()(const variant_t& a, const variant_t& b) const
        { return boost::apply_visitor(_helper, a, b); }
    };
}

CUSTOM_STRONG_TYPEDEF(detail::variant_t, custom_vt);

namespace 
{
    bool operator<(const custom_vt& a, const custom_vt& b)
        { return detail::variant_less()(a, b); }

    std::ostream& operator<<(std::ostream& os, const custom_vt& v)
        { return os << (const detail::variant_t&)v; }
}

int main()
{
    const detail::variant_t I(43), S("42");
    const custom_vt i(I), s(S);

    // regression test (compare to boost behaviour)
    std::cout << "boost:   " << I << " < " << S << ": " << std::boolalpha << (I<S) << "\n";
    std::cout << "boost:   " << S << " < " << I << ": " << std::boolalpha << (S<I) << "\n";

    // FIX1: clumsy syntax (works for boost native variants)
    detail::variant_less pred;
    std::cout << "clumsy:  " << i << " < " << s << ": " << std::boolalpha << pred(i,s) << "\n";
    std::cout << "clumsy:  " << s << " < " << i << ": " << std::boolalpha << pred(s,i) << "\n";

    std::cout << "clumsy:  " << I << " < " << S << ": " << std::boolalpha << pred(I,S) << "\n";
    std::cout << "clumsy:  " << S << " < " << I << ": " << std::boolalpha << pred(S,I) << "\n";

    // FIX2: neat syntax (requires a custom type wrapper)
    std::cout << "custom:  " << i << " < " << s << ": " << std::boolalpha << (i<s) << "\n";
    std::cout << "custom:  " << s << " < " << i << ": " << std::boolalpha << (s<i) << "\n";

}

Выход:

boost:   43 < 42: true
boost:   42 < 43: false
clumsy:  43 < 42: false
clumsy:  42 < 43: true
clumsy:  43 < 42: false
clumsy:  42 < 43: true
custom:  43 < 42: false
custom:  42 < 43: true

Теперь, конечно, могут быть неудачные взаимодействия, если вы хотите передать свой custom_vt в библиотечные API, которые используют TMP для работы с вариантами. Однако из-за безболезненных преобразований между ними вы должны быть в состоянии «пробиться», используя detail :: variable_t в подходящее время.

Это цена, которую вы должны заплатить за удобство синтаксиса на сайте вызова.

...