Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

automatic conversion of bool to nullptr_t

Tags:

c++

c++11

I have the following code with a custom Variant class and a custom SmartPtr class:

using namespace std;

class Object
{
public:
};

template<typename T>
class SmartPtr
{
public:

    template<typename Y>
    explicit SmartPtr(Y* p) { p_ = p; }

    SmartPtr(std::nullptr_t) { p_ = nullptr; }

private:
    T* p_;
};

class Variant
{
public:
    Variant(bool b) : _b(b) { }

private:
    bool _b;
};

class Obj
{
public:
    void test(SmartPtr<Object> /*p*/) { cout << "smartptr version!" << endl; }
    void test(Variant /*v*/) { cout << "variant version!" << endl; }
};

int main(int argc, const char *argv[])
{
    Obj o;
    o.test(nullptr); // calls SmartPtr version
    o.test(true); // calls Variant version
    o.test(false); // -> compiler error: ambiguous call to overloaded function

    return 0;
}

I assume that the boolean false can be converted both to the Variant and to 0 then to nullptr and then to SmartPtr, which causes this error.

Any chances of avoiding this conversion?

For the user of the library an API which works with 'o.test(true);' but requires something like 'o.test(Variant(false));' to compile is not very intuitive.

like image 381
mgr Avatar asked Nov 10 '22 02:11

mgr


1 Answers

I believe I have an ideal solution. It only requires that the test function be altered, so it leaves SmartPtr and Variant alone, which is ideal. It adds a non-defined templated overload to test that has specializations for bool and nullptr that are defined. This directly dispatches bool and nullptr to the desired specialization, but causes link errors on other unhandled types. I'm so glad to have this worked out because I've certainly run into this in many forms myself. I wish you could use explicit of function parameters!!

I got the idea from here: C++ templates that accept only certain types

using namespace std;

class Object
{
public:
};

class Variant
{
public:
    Variant( bool b) : _b(b) { }

private:
    bool _b;
};

template<typename T>
class SmartPtr
{
public:
    SmartPtr(std::nullptr_t null) { p_ = nullptr; }

    template<typename Y>
    SmartPtr(Y* p) { p_ = p; }

private:
    T* p_;
};

class Obj
{
public:
    void test(SmartPtr<Object> here /*p*/) {
        cout << "smartptr version!" << endl;
    }
    void test(Variant /*v*/) { cout << "variant version!" << endl; }

    template<typename T> void test(T t);

    template<>
    void test<bool>(bool b) {
        cout << "bool specialization" << endl;
        test(Variant(b));
    }

    template<>
    void test<std::nullptr_t>(std::nullptr_t null) {
        cout << "nullptr specialization" << endl;
        test(SmartPtr<Object>(nullptr));
    }
};

int main(int argc, const char *argv[])
{
    Obj o;
    Obj c;
    Object object;

    //o.test(3);    // Gives link error LNK2019

    o.test(Variant(true)); // calls Variant version
    o.test(SmartPtr<Object>(&object)); // calls SmartPtr version
    o.test(nullptr); // dispatched to SmartPtr version by nullptr specialization
    o.test(true); // dispatched to Variant version by bool specialization
    o.test(false); // dispatched to Variant version by bool specialization
    return 0;
}

I had already answered with something not ideal, so I leave that answer in tact as what follows:

=============================================

I don't have an ideal solution here, and I don't know the constraints you have on your code so this may not be of functional use to you, but the following is sensible. It disallows code to use nullptr at compile time and relies on a global null_smart constant to be used in all cases where the caller is simply showing no interest in passing an object.

#include <iostream>

using namespace std;

class Object
{
public:
};

class Variant
{
public:
    Variant(bool b) : _b(b) { }
private:
    Variant(std::nullptr_t) {};

private:
    bool _b;
};

template<typename T>
class SmartPtr
{
public:
    SmartPtr() { p_ = nullptr; }

    template<typename Y>
    SmartPtr(Y* p) { p_ = p; }

private:
    T* p_;
};

class Obj
{
public:
    void test(SmartPtr<Object> /*p*/) { cout << "smartptr version!" << endl; }
    void test(Variant /*v*/) { cout << "variant version!" << endl; }
};

const SmartPtr<Object> null_smart;

int main(int argc, const char *argv[])
{
    Obj o;
    o.test(null_smart); // calls SmartPtr version, without interest in passing object
    o.test(true); // calls Variant version
    o.test(false); // calls Variant version
    return 0;
}

It's cleaner than the true/Variant(false) issue, but still a bit on the picky side.

like image 186
Charles J. Daniels Avatar answered Nov 15 '22 13:11

Charles J. Daniels