C ++ и typetraits: самый простой способ определения списка возможных определений - PullRequest
1 голос
/ 05 марта 2012

Я хочу определить функцию template<typename T> T constCast(const ScriptVar_t& s);. В зависимости от T, я хочу иметь разные определения. (ScriptVar_t является классом, но детали здесь не важны в этом контексте.)

Условия T не так просты, как конкретные типы, все они являются несколько более сложными статическими логическими выражениями. То есть У меня есть список выражений ext1 .. extN, и для каждого из них у меня есть определение этой функции. И я хочу, чтобы они были проверены в таком порядке, и должно использоваться определение первого подходящего выражения. Если все они терпят неудачу, я хочу получить ошибку компилятора.

Прямо сейчас, у меня просто есть 2 определения, и мой код выглядит следующим образом (это полный тестовый пример, соответствующий код помечен):

#include <boost/type_traits.hpp>

enum {
    SVT_INT,
    SVT_FLOAT,
    SVT_BASEOBJ,
    SVT_CUSTOMVAR
};

struct BaseObject {};
struct CustomVar {};

template<typename T> struct GetType;
template<> struct GetType<int> { static const int value = SVT_INT; };
template<> struct GetType<float> { static const int value = SVT_FLOAT; };
template<> struct GetType<BaseObject> { static const int value = SVT_BASEOBJ; };

template<bool> struct GetType_BaseCustomVar;
template<> struct GetType_BaseCustomVar<true> {
    struct Type { static const int value = SVT_CUSTOMVAR; };
};
template<typename T> struct GetType : GetType_BaseCustomVar<boost::is_base_of<CustomVar,T>::value>::Type {};

struct ScriptVar_t;
template<typename T> T CastScriptVarConst(const ScriptVar_t& s);

struct ScriptVar_t {
    operator int() const { return 0; }
    operator float() const { return 0.0f; }
    operator BaseObject() const { return BaseObject(); }
    template<typename T> T* as() const { return NULL; }

    template <typename T> T castConst() const { return CastScriptVarConst<T>(*this); }
};

// *** relevant code starts here

template<typename T> T CastScriptVarConst(const ScriptVar_t& s);

template<bool> struct CastScriptVar1;
template<typename T> struct CastScriptVar1_IsSimpleType {
    static const bool value = GetType<T>::value < SVT_BASEOBJ;
};
template<> struct CastScriptVar1<true> {
    template<typename T> static T castConst(const ScriptVar_t& s, const T& /*dummy*/) { return (T) s; }
};

template<bool> struct CastScriptVar2;
template<typename T> struct CastScriptVar2_IsCustomVar {
    static const bool value = boost::is_base_of<CustomVar,T>::value;
};
template<> struct CastScriptVar2<true> {
    template<typename T> static T castConst(const ScriptVar_t& s, const T& /*dummy*/) { return *s.as<T>(); }
};

template<> struct CastScriptVar1<false> {
    template<typename T> static T castConst(const ScriptVar_t& s, const T& /*dummy*/) {
        return CastScriptVar2<CastScriptVar2_IsCustomVar<T>::value>::castConst(s, T());
    }
};
template<typename T> T CastScriptVarConst(const ScriptVar_t& s) {
    return CastScriptVar1<CastScriptVar1_IsSimpleType<T>::value>::castConst(s, T());
}

int main() {
    ScriptVar_t v;
    v.castConst<int>();
    v.castConst<CustomVar>();
}

Я придумал это после нескольких попыток, пока он не заработал.

(Как видно из кода, двумя выражениями являются GetType<T>::value < SVT_BASEOBJ и boost::is_base_of<CustomVar,T>::value. Если оба имеют значение false, компилятор должен выдать ошибку. Но это только пример для моего вопроса.)

Интересно, есть ли несколько более чистое решение для этого кода.


Для справки, я играю с ним здесь . И сейчас у меня снова есть несколько иное решение для всех других решений здесь.

Ответы [ 3 ]

2 голосов
/ 05 марта 2012

Так что, если я правильно понял, у вас есть два метода приведения.Если GetType<T>::value < SVT_BASEOBJ, то вы просто хотите использовать обычное приведение: (T) s;

С другой стороны, если GetType<T>::value < SVT_BASEOBJ имеет значение false, вы хотите убедиться, что CustomVar является базовым классом типаT (то есть T происходит от CustomVar).Затем вы хотите использовать функцию-член для этого объекта: *s.as<T>()

В противном случае вы хотите ошибку компиляции.

Вот один из способов сделать это, используя перегрузку и std::enable_if:

template<typename T>
typename std::enable_if<GetType<T>::value < SVT_BASEOBJ,T>::type
CastScriptVarConst(const ScriptVar_t& s) {
    return (T) s;
}

template<typename T>
typename std::enable_if<!(GetType<T>::value < SVT_BASEOBJ)
                        && std::is_base_of<CustomVar,T>::value,T>::type
CastScriptVarConst(const ScriptVar_t& s) {
    return *s.as<T>();
}

enable_if использует правило SFINAE в C ++, поэтому, если условия не выполняются, единственным последствием является то, что функция не добавляется в набор допустимых перегрузок для этого вызова.Поскольку условия enable_if являются взаимоисключающими, в большинстве случаев одна из функций будет жизнеспособной для любого данного вызова, и поэтому никогда не будет двусмысленности.И если ни одно из условий не выполняется, вы получите ошибку компиляции, в которой говорится, что не удалось найти подходящую функцию.


#include <type_traits>
#include <iostream>

enum {
    SVT_INT,
    SVT_FLOAT,
    SVT_BASEOBJ,
    SVT_CUSTOMVAR
};

struct BaseObject {};
struct CustomVar {};

template<typename T> struct GetType;
template<> struct GetType<int> { static const int value = SVT_INT; };
template<> struct GetType<float> { static const int value = SVT_FLOAT; };
template<> struct GetType<BaseObject> { static const int value = SVT_BASEOBJ; };

template<bool> struct GetType_BaseCustomVar;
template<> struct GetType_BaseCustomVar<true> {
    struct Type { static const int value = SVT_CUSTOMVAR; };
};
template<typename T> struct GetType : GetType_BaseCustomVar<std::is_base_of<CustomVar,T>::value>::Type {};

struct ScriptVar_t {
    operator int() const { return 0; }
    operator float() const { return 0.0f; }
    operator BaseObject() const { return BaseObject(); }
    template<typename T> T* as() const { return NULL; }

    template <typename T> T castConst() const;
};

template<typename T>
typename std::enable_if<GetType<T>::value < SVT_BASEOBJ,T>::type
CastScriptVarConst(const ScriptVar_t& s) {
    std::cout << "value < SVT_BASEOBJT\n";
    return (T) s;
}

template<typename T>
typename std::enable_if<!(GetType<T>::value < SVT_BASEOBJ)
&& std::is_base_of<CustomVar,T>::value,T>::type
CastScriptVarConst(const ScriptVar_t& s) {
    std::cout << "CustomVar\n";
    return *s.as<T>();
}

template <typename T>
T ScriptVar_t::castConst() const {
    return CastScriptVarConst<T>(*this);
}

int main() {
    ScriptVar_t v;
    v.castConst<int>();
    v.castConst<CustomVar>();
}
1 голос
/ 08 марта 2012

Если бы я правильно понял, я бы использовал справочную таблицу для Cast-функторы и мета-функция для вычисления смещения в стол.

Альтернативой может быть использование справочной таблицы на основе типов, состоящей из тегов и функторов. pick_cast тогда выберет правильный тег вместо int. Это может быть легче читать, если решение стол становится большим.

#include <boost/type_traits.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/at.hpp>

struct BaseObject {};
struct CustomVar {};

namespace mpl = boost::mpl;

struct ScriptVar_t {
    operator int() const { return 0; }
    operator float() const { return 0.0f; }
    operator BaseObject() const { return BaseObject(); }
    template<typename T> T* as() const { return NULL; }
    template <typename T> T castConst() const;
};

struct default_cast {
  template<typename T>
  T operator()(const ScriptVar_t& s) const { return (T) s; }
};

struct base_cast {
  template<typename T>
  T operator()(const ScriptVar_t& s) const { return *s.as<T>(); }
};

typedef mpl::vector< default_cast, base_cast > casts;

enum {
  DEFAULT = 0,
  BASE,
  END_OF_ENUM
};

// pick the right cast for T
template<typename T>
struct pick_cast {
  typedef typename mpl::if_< typename boost::is_base_of<CustomVar,T>::type,
                             mpl::int_<BASE>, mpl::int_<DEFAULT> >::type type;
};

template <typename T> T ScriptVar_t::castConst() const { 
  typedef typename mpl::at<casts, typename pick_cast<T>::type>::type func;
  return func().template operator()<T>(*this);
}

int main() {
    ScriptVar_t v;
    v.castConst<int>();
    v.castConst<CustomVar>();
}
1 голос
/ 05 марта 2012

Я не уверен, правильно ли я вас понимаю, но эта метапрограмма может сделать то, что вам нужно:

// Just a placeholder type for default template arguments
struct NoType { };

// Template to check whether a type is NoType. You can replace this with boost
// is_same<T, NoType> if you like.
template <typename T>
struct IsNoType  {
    static const bool value = false;
};

template <>
struct IsNoType<NoType>  {
    static const bool value = true;
};


// Template for matching the expressions and their corresponding types
// This could be done more nicely using variadic templates but no MSVC
// version supports them currently.
// You can specify up to 8 conditions and types. If you specify more,
// the code will break :) You can add more easily by just expanding the
// number of lines and parameters though.
template <
    bool p0 = false, typename t0 = NoType,
    bool p1 = false, typename t1 = NoType,
    bool p2 = false, typename t2 = NoType,
    bool p3 = false, typename t3 = NoType,
    bool p4 = false, typename t4 = NoType,
    bool p5 = false, typename t5 = NoType,
    bool p6 = false, typename t6 = NoType,
    bool p7 = false, typename t7 = NoType,

    // This must not be changed/overriden/specified, it is used as a condition to stop the compiler loop, see below
    bool stop = true, typename stopT = NoType  
>
struct GetFirstMatchingType  {

};

// Specialization when the first element in the expression list is true.
// When this happens, we just return the first type as the ::type typedef.
template <
    typename t0,
    bool p1, typename t1,
    bool p2, typename t2,
    bool p3, typename t3,
    bool p4, typename t4,
    bool p5, typename t5,
    bool p6, typename t6,
    bool p7, typename t7,
    bool p8, typename t8
>
struct GetFirstMatchingType<true, t0, p1, t1, p2, t2, p3, t3, p4, t4, p5, t5, p6, t6, p7, t7, p8, t8>  {
    typedef t0 type;

    // Check that the type is not NoType. If it is, it means all arguments are false and we should fail.
    // In case of a non-C++11 compiler, you can throw any other compiler error here or use BOOST_STATIC_ASSERT
    static_assert(!IsNoType<t0>::value, "No expression has been matched, don't know what type to return!");
};

// Specialization when the first type is false. If this happens, we cyclically rotate
// the sequence so that p0, t0 becomes p8, t8. The compiler keeps expanding this
// until it finds true as the first element. Note that this will always happen because
// the stop argument in the base template is set to true.
template <
    typename t0,
    bool p1, typename t1,
    bool p2, typename t2,
    bool p3, typename t3,
    bool p4, typename t4,
    bool p5, typename t5,
    bool p6, typename t6,
    bool p7, typename t7,
    bool p8, typename t8
>
struct GetFirstMatchingType<false, t0, p1, t1, p2, t2, p3, t3, p4, t4, p5, t5, p6, t6, p7, t7, p8, t8>  {
    typedef typename GetFirstMatchingType<p1, t1, p2, t2, p3, t3, p4, t4, p5, t5, p6, t6, p7, t7, p8, t8, false, t0>::type type;
};

int main()  {

// Evaluates to int myVar1 if int is 4 bytes, or __int32 myVar1 if __int32 is 4 bytes and int is not 4 bytes
GetFirstMatchingType<
    sizeof(int) == 4, int,
    sizeof(__int32) == 4, __int32
>::type myVar1;

// Evaluates to short myVar on my platform
GetFirstMatchingType<
    sizeof(int) == 5, int,
    sizeof(short) == 2, short
>::type myVar2;

// Also evaluates to short myVar on my platform
GetFirstMatchingType<
    sizeof(int) == 5, int,
    sizeof(short) == 2, short,
    sizeof(int) == 4, int
>::type myVar3;

// Throws an error (error C2338: No expression has been matched, don't know what type to return!)
GetFirstMatchingType<
    sizeof(int) == 5, int,
    sizeof(long) == 5, long,
    sizeof(short) == 3, short
>::type myVar4;
}

Протестировано на MSVC 2010, но оно должно хорошо работать на любом C ++-совместимом компиляторе, таком как GCC или Clang.

РЕДАКТИРОВАТЬВот пример решения вашего вопроса с использованием приведенного выше кода:

struct ScriptVar_t;

struct CastScriptVar1 {
    template<typename T> static T castConst(const ScriptVar_t& s) { return (T) s; }
};

struct CastScriptVar2 {
    template<typename T> static T castConst(const ScriptVar_t& s) { return *s.as<T>(); }
};

struct ScriptVar_t {
    operator int() const { return 0; }
    operator float() const { return 0.0f; }
    operator BaseObject() const { return BaseObject(); }
    template<typename T> T* as() const { return NULL; }

    template <typename T> T castConst() const { 
        return GetFirstMatchingType<
            !boost::is_base_of<CustomVar, T>::value, CastScriptVar1,
            GetType<T>::value >= SVT_BASEOBJ, CastScriptVar2
            // Add more conditions & casts here
        >::type::castConst<T>(*this);
    }
};

int main()
{
    ScriptVar_t v;
    v.castConst<int>();
    v.castConst<CustomVar>();

    return 0;
}

Его можно переписать, используя boost :: tuple и boost :: mpl, чтобы избавиться от странных вариационных шаблонов.

РЕДАКТИРОВАТЬ 2: кажется, мой предыдущий EDIT исчез, я положил его обратно

...