C++和typetraits:定义可能定义列表的最简单方法

C++ and typetraits: simplest way of defining a list of possible definitions

本文关键字:定义 列表 最简单 方法 typetraits C++      更新时间:2023-10-16

我想定义一个函数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_BASEOBJboost::is_base_of<CustomVar,T>::value。如果两者都为false,编译器应该会抛出错误。但这只是我问题的一个例子。)

我想知道这个代码是否有一个更干净的解决方案。


作为参考,我在这里玩它。现在,我又有了一个与这里所有其他解决方案有些不同的解决方案。

因此,如果我理解正确,您有两种类型转换方法。如果是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利用C++中的SFINAE规则,因此如果条件失败,唯一的结果是函数没有添加到该调用的可行重载集。由于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_BASEOBJTn";
    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 << "CustomVarn";
    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>();
}

如果我理解正确,我会使用强制转换函子和一个元函数,以将偏移量计算到桌子

另一种选择是使用基于类型的查找表,该表由标签和函子。pick_cast会选择正确的标签而不是CCD_ 21。如果决定桌子变大了。

#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>();
}

我不确定我是否正确理解你,但这个元程序可以满足你的需要:

// 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:我以前的编辑似乎消失了,我把它放回