Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I prevent boost::optional<T> from being constructed erroneously with 0?

boost::optional<T> (1.51) provides a way of constructing objects that is very dangerous for my users and that I'd like to prevent. Let's say I have my own integer class and I want to pass an optional such integer and store it in some class:

class myint {
public:
    int m_a;
    myint (int r_a) : m_a(r_a) {
    }
};

struct myclass {
    boost::optional<myint> content;
    myclass (const boost::optional<myint>& arg) : content(arg) {
    }
};

and now, here's how users would use the class:

myclass(myint(13));            //correct use
myclass(boost::none);          //correct use
myclass(myint(0));             //correct use
myclass(0);                    //INCORRECT use, this easy typo
                               //equates boost::none which
                               //is not what the user meant

I'd like to understand what is going on here and prevent this behaviour.


Interestingly,

myclass(1);              //does not compile

boost::none is totally a valid value for my field, but having a boost::none sneak-up when the user is trying to type in a 0 is horrendously misleading and dangerous.

The intent might be a bit hidden since I'm not really rolling out a myint class and I don't really have a class myclass that serves little to no purpose. Anyways I need to send 10 or so optional ints to a function and deduping wouldn't work. (you can imagine I asked you for your age, your height and your wealth and that there's three special buttons to check if you don't want to answer a question)


I've posted an answer that seems to work below (built from Mooing's Duck & Ilonesmiz suggestion, but lighter). I'm happy to hear comments about it, though.

like image 549
Gurg Hackpof Avatar asked Mar 06 '13 18:03

Gurg Hackpof


3 Answers

Do not let the constructor take a boost::optional I would do something like this instead.

struct myclass {
    boost::optional<myint> content;
    myclass () = default;
    explicit myclass(const myint& int_):content(int_){}
};

However when I am thinking about it I am not completely clear on what you are trying to achieve and what you want to avoid happening. What is the purpose of the optional member?

like image 116
AxelOmega Avatar answered Oct 20 '22 13:10

AxelOmega


This is uglier than I like, but it seems to address your concerns. It works by forwarding the argument given to myclass perfectly to a pair of functions that take either an int or a boost::none_t, bypassing the implicit user-defined constructor. This works because 0 matches int better than boost::none_t, and an implicit user-defined constructor is the worst match.

class myint {
public:
    int m_a;
    myint (int r_a) : m_a(r_a) {}
};    
boost::optional<myint> myintctor(int arg) {return myint(arg);}
boost::optional<myint> myintctor(boost::none_t arg) {return arg;}

struct myclass {
    boost::optional<myint> content0;
    boost::optional<myint> content1;
    boost::optional<myint> content2;

    template<class T0, class T1, class T2>
    myclass(const T0& a0, const T1& a1, const T2& a2) 
    :content0(myintctor(a0)), content1(myintctor(a1)), content2(myintctor(a2))
    {}
};

Proof of concept. Modern compilers ought to be smart enough to elide the copy, but that shouldn't matter for an int.

like image 26
Mooing Duck Avatar answered Oct 20 '22 14:10

Mooing Duck


I guess the problem is only meaningful for optional int. One solution could be to provide two constructors:

myclass() : content(boost::none) {}
myclass(myint input) : content(input) {}

It's true that you lose a bit the advantage of boost::optional...

like image 1
FredericS Avatar answered Oct 20 '22 14:10

FredericS