Question

I want to define a function template<typename T> T constCast(const ScriptVar_t& s);. Depending on T, I want to have different definitions. (ScriptVar_t is a class but details are not important here in this context.)

The conditions on T aren't as simple as specific types, they are all somewhat more complicated static boolean expressions. I.e. I have a list of expressions ext1..extN and for each, I have a definition of that function. And I want to have them checked in that order and the definition of the first matching expression should be used. If all of them fail, I want to get a compiler error.

Right now, I just have 2 definitions and my code looks like this (this is a full test case, the relevant code is marked):

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

I came up with this after a few tries until it works.

(As you can see from the code, the two expressions are GetType<T>::value < SVT_BASEOBJ and boost::is_base_of<CustomVar,T>::value. If both are false, the compiler should throw an error. But this is only an example for my question.)

I wonder if there is a somewhat more clean solution for this code.


For reference, I am playing with it here. And right now, I have again a somewhat different solution to all the other solutions here.

Was it helpful?

Solution

If I understood correctly, I'd use a look-up table for the cast-functors and a meta-function to calculate the offset into the table.

An alternative would be to use a type based look-up table consisting of tags and functors. pick_cast would then choose the right tag instead of an int. This might be easier to read if the decision table becomes large.

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

OTHER TIPS

So if I understood correctly, you have two methods of casting. If GetType<T>::value < SVT_BASEOBJ then you just want to use a normal cast: (T) s;

On the other hand, if GetType<T>::value < SVT_BASEOBJ is false then you want to ensure that the CustomVar is a base class of type T (i.e., T derives from CustomVar). Then you want to use a member function on that object: *s.as<T>()

Otherwise you want a compile error.

Here's one way to do that using overloading and 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 takes advantage of the SFINAE rule in C++ so that if the conditions fail the only consequence is that the function is not added to the set of viable overloads for that call. Since the enable_if conditions are mutually exclusive at most one of the function will ever be viable for any given call, and so there'll never be ambiguity. And if neither condition is true then you'll get a compile error saying it couldn't find a matching function.


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

I am not sure if I understand you correctly but this metaprogram could do what you need:

// 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;
}

Tested on MSVC 2010 but it should work well on any C++ compliant compiler such as GCC or Clang.

EDIT
Here's an example of a solution to your question using the above code:

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;
}

It could be rewritten using boost::tuple and boost::mpl to get rid of the weird variadic templates.

EDIT 2: it seems my previous EDIT disappeared, I put it back

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top