Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I strongly typedef non-primitive types?

Observe the following program in which a function accepts both the expected type and any type that is a typedef of that type.

//a user defined type
class Widget{};

//a function that takes a Widget
void function (Widget w){}

int main(){

    //make a typedef (this is C++11 syntax for a typedef. It's the same thing)
    using Gadget = Widget;

    //make the two "different types" (well.. they're not really different as you will see)
    Widget w;
    Gadget g;

    //call a function that should ONLY accept Widgets
    function(w); //works (good)
    function(g); //<- works (I do not want this to compile though)

}

As you can see, a typedef does not actually distinguish a new type. I thought instead to inherit from the type:

//inherit instead
class Gadget: public Widget{};

//make the two "different types"
Widget w;
Gadget g;

//call the function that should ONLY accept widgets
function(w); //works (good)
function(g); //<- works (I do not want this to compile though)

Same problem.
Looking at boost, I thought to try a strong typedef:

#include <boost/serialization/strong_typedef.hpp>

//a user defined type
class Widget{};

//a function that takes the user defined type
void function (Widget w){}

int main(){

    //try to strongly typedef
    BOOST_STRONG_TYPEDEF(Widget, Gadget)

    //make the two "different types"
    Widget w;
    Gadget g;

    //call the function that should ONLY accept widgets
    function(w);
    function(g);

}

compile errors:

In member function ‘bool main()::Gadget::operator==(const main()::Gadget&) const’:
error: no match for ‘operator==’ (operand types are ‘const Widget’ and ‘const Widget’)
  BOOST_STRONG_TYPEDEF(Widget, Gadget)
  ^
In member function ‘bool main()::Gadget::operator<(const main()::Gadget&) const’:
error: no match for ‘operator<’ (operand types are ‘const Widget’ and ‘const Widget’)
  BOOST_STRONG_TYPEDEF(Widget, Gadget)
  ^

Apparently BOOST_STRONG_TYPEDEF only works on primitive types.
I tried to do inheritance again, but stop the implicit conversion:

//I want the functionality, but these are NOT the same type!
class Gadget: public Widget{
    operator Widget() = delete;
};

That did not work either.

Questions:

  1. Why does boost strong_typedef only work on primitive types?
  2. How can I 'typedef' a non-primitive type to get functionality similar to boost strong_typef?
like image 785
Trevor Hickey Avatar asked Apr 21 '14 22:04

Trevor Hickey


3 Answers

Basically you need two unrelated classes with the same behavior. I would use a parametrized template for that:

template<int tag> class WidgetGadget { ... };
typedef WidgetGadget<0> Widget;
typedef WidgetGadget<1> Gadget;
like image 170
grep Avatar answered Oct 24 '22 03:10

grep


BOOST_STRONG_TYPEDEF actually assumes that types are equatable (==), assignable (=) and less-than-comparable (<).

If your type isn't, then the macro results in code that doesn't compile, as you have witnessed. You can roll your own macro or provide implementations for the required operations.

You can find a CUSTOM_STRONG_TYPEDEF in this answer from februari 2012: How to use comparison operators on variant with contained types?, which explicitly avoids getting the default comparison behaviour

Update Made the example more explicit for your use case, see it Live On Coliru

//a user defined type
class Widget{};
class Frobnicator{};

/////////////////////////////////////////////////////
// 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;}          \
    explicit operator const T & () const {return t; }               \
    explicit operator T & () { return t; }                          \
    /*bool operator==(const D & rhs) const { return t == rhs.t; } */\
    /*bool operator<(const D & rhs) const { return t < rhs.t; }   */\
};

CUSTOM_STRONG_TYPEDEF(Widget, Gadget)
CUSTOM_STRONG_TYPEDEF(Frobnicator, Normalcy)

void acceptWidget(Widget){}
void acceptGadget(Gadget){}
void acceptFrobnicator(Frobnicator){}
void acceptNormalcy(Normalcy){}

int main(){

    //make the two "different types" (well.. they're not really different as you will see)
    Widget w;
    Gadget g;

    //call a function that should ONLY accept Widgets
    acceptWidget(w); //works (good)
    acceptGadget(g);

    //acceptWidget(g); // Error
    //acceptGadget(w); // Error
    // but we can enjoy conversions if we summon them
    acceptWidget(static_cast<Widget&>(g));

    Frobnicator f;
    Normalcy n;
    acceptFrobnicator(f);
    acceptNormalcy(n);

}
like image 26
sehe Avatar answered Oct 24 '22 03:10

sehe


Maybe you could use private inheritance and some usings?

class Gadget : Widget { using Widget::Widget; using Widget::foo; ... };
like image 44
Daniel Frey Avatar answered Oct 24 '22 05:10

Daniel Frey