2

I've not been able to find an explanation of the following on Google so far, and it's confusing me a little.

I have a Scene which stores hierarchies of SceneObjects. The Scene acts as a templated SceneObject factory, so that bookkeeping can be done when a SceneObject subclass instance is created or deleted. Both of these classes are in a dynamically linked module of their own and are within a module namespace (not sure whether this matters).

The (simplified) SceneObject class looks like this:

// SceneObject is a base class, but is not pure.
class SceneObject
{
    // The parent Scene needs to be able to access protected functions.
    friend class Scene;

protected:
    // This is protected to enforce the factory design pattern.
    // We don't want SceneObjects created without being tied to a Scene.
    SceneObject(Scene* parentScene, SceneObject* parentObject);

public:
    ...
};

And the (simplified) Scene class looks like this:

class Scene
{
public:

    // General bookkeeping - a fully constructed subclass is required
    // here as the subclass constructor sets certain member variables.
    void processSceneObjectCreated(SceneObject* object);

    // This function means we can do:
    //   SceneObjectSub* obj = scene.createSceneObject<SceneObjectSub>(...)
    // and pass whatever parameters are required for that particular
    // subclass' constructor, while ensuring the Scene can keep a record
    // of the created object.
    //
    // We can't call processSceneObjectCreated() in the SceneObject base
    // class' constructor, as the required subclass constructor will not
    // have been run yet.
    template<typename T, typename... Args>
    T* createSceneObject(Args... args)
    {
        T* obj = new T(this, std::move(args)...);
        processSceneObjectCreated(obj);
        return obj;
    }

    ...
};

As a test, I compiled the following code to create a new SceneObject:

ModuleNS::Scene scene;
ModuleNS::SceneObject* sceneObject =
    scene.createSceneObject<ModuleNS::SceneObject>(NULL);

However, the MSVC compiler gave me the following error:

Cannot convert argument 2 from 'int' to 'ModuleNS::SceneObject*'

This confused me, as I thought NULL (ie. 0) could always be converted to a pointer type. If instead I use static_cast<ModuleNS::SceneObject*>(NULL), or nullptr (which I'd like to use, but for the sake of consistency with old code I've been using NULL instead), the compile error goes away.

What specifically causes NULL to stop being castable to a pointer?

2
  • pointer can be 64-bit. And the integer can be 32 bit? check here: stackoverflow.com/questions/1674150/… Commented Oct 2, 2016 at 16:25
  • I am on a 64-bit system, so that probably does apply. I've never had the problem before in any other code in the project though, so am curious what it is about this code specifically that causes the error. Commented Oct 2, 2016 at 16:26

2 Answers 2

9

The error has the same nature as the one in the following sample

template <typename T> void foo(T t) {
    void *p = t; // ERROR here
}

int main() {
    foo(NULL);
}

In C++ only literal 0 can be converted to pointer type to produce null-pointer value. NULL expands to a literal zero, which is why you can use it to initialize/assign/compare with pointers directly. "Directly" is the key word here.

But once you feed it through a function parameter, it is no longer a literal 0 and can no longer act as a null-pointer constant.

In the above example T is deduced as some integer type. Inside the function t is just a [run-time] integer that happens to have value 0. And you are not allowed to initialize pointers with arbitrary integers.

Note that in modern C++ NULL can actually be defined as nullptr, which will make the above code to compile. But with the "traditional" definition of NULL (as integral 0) it won't.

Sign up to request clarification or add additional context in comments.

Comments

1

Replace NULL with C++'s nullptr.

The 0 value that NULL expands to gets "lost in translation", when its gets laundered through std::move.

5 Comments

Can you maybe expand a bit more what ""lost in translation"" means?
So std::move is the culprit? I didn't know it could have effects like that, but I guess that makes sense. I might go back and re-work my code to use nullptr consistently in that case, it'll probably be worth the effort.
std::move has nothing to do with it. The error is caused by the fact that NULL eligibility to act as a null-pointer constant does not survive passing through function argument list.
I assume NULL being 0 is type-deduced by the template system as an integer argument. Once its type is set to integer an int argument is generated by the template. That's where it got lost in translation I think.
std::move could cause errors like this, because it is also a template function, so std::move(NULL) will return an int and not nullptr_t. But yeah in the code example, even if std::move were removed you would still have a problem as AnT explains.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.