5

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.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
mgr
  • 344
  • 4
  • 11
  • Different compilers treat this code differently. g++(c++1y) fails at both `nullptr` and `false`. clang(c++1y) fails at `nullptr`. Related question: http://stackoverflow.com/questions/17501942/false-implicitly-convert-to-null-pointer/ – zch Aug 28 '14 at 12:56
  • I think that standard compliant compiler should accept entire code (`nullptr_t -> SmartPtr` being better than `nullptr_t -> bool -> Variant` and `bool -> nullptr_t -> SmartPtr` being invalid), but I guess it is not much help to you. I would recommend using separate function names. – zch Aug 28 '14 at 13:24
  • @zsh: I am using both clang and g++ with std=c++11 on linux as well as vc12 on windows, the error message are the same. Unfortunately, a different naming is out of question as the code was working before and I try to introduce the nullptr_t constructor for the SmartPtr class which makes the compile fail. – mgr Aug 29 '14 at 07:20

1 Answers1

1

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.

Community
  • 1
  • 1
  • The Object with the test() method is in user code, which existed before my idea to have a uniform null pointer syntax for all raw and custom SmartPtr classes. – mgr Sep 01 '14 at 11:30