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?

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
NoodleCollie
  • 855
  • 7
  • 24
  • pointer can be 64-bit. And the integer can be 32 bit? check here: http://stackoverflow.com/questions/1674150/null-definition-problem-on-64-bit-system – Jean-François Fabre Oct 02 '16 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. – NoodleCollie Oct 02 '16 at 16:26

2 Answers2

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.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
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.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • 1
    Can you maybe expand a bit more what ""lost in translation"" means? – Rakete1111 Oct 02 '16 at 16:30
  • 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. – NoodleCollie Oct 02 '16 at 16:32
  • 4
    `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. – AnT stands with Russia Oct 02 '16 at 16:46
  • 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. – Galik Oct 02 '16 at 17:22
  • `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. – Chris Beck Oct 02 '16 at 17:56