2

I have a class that I use for purely syntactic purposes, to call a function in a certain way. This is a simplified example:

#include<iostream>

template<class T1>
struct make{
    template<class T2>
    static T1 from(T2 const& t2){
        return T1{}; //or something more complicated
    }
};

int main(){
    double d = make<double>::from(2);
    std::cout << d << '\n';
}

Now, suppose I want to warn the user that this class should not be instantiated. There may be uses for the class to be instatiable but I have the curiosity if it is possible to forbid that?

First I tried deleting the default constructor

template<class T1>
struct make{
    make() = delete;
    template<class T2>
    static T1 from(T2 const& t2){
        return T1{}; //or something more complicated
    }
};

But then this is still possible:

make<double> m{}; // valid

Finally, I tried deleting the destructor and that seemed to work

template<class T1>
struct make{
    ~make() = delete;
    template<class T2>
    static T1 from(T2 const& t2){
        return T1{}; //or something more complicated
    }
};

But it seems that then the class can still be allocated by new.

Should I delete both the destructor and the delete constructor, (what about the copy and move constructor?)

Is this the best way to disallow instantiation? code here: http://coliru.stacked-crooked.com/a/0299c377c129fffb

#include<iostream>

template<class T1>
struct make{
    make() = delete;
    ~make() = delete;
    template<class T2>
    static T1 from(T2 const& t2){
        return T1{}; //or something more complicated
    }
};

int main(){
    double d = make<double>::from(2);
    std::cout << d << '\n';
    make<double> m{}; // compile error (no destructor)
    auto p = new make<double>{}; // compile error (no constructor)
}
alfC
  • 14,261
  • 4
  • 67
  • 118

1 Answers1

3

But then this is still possible:

make<double> m{}; // valid

... I did not know that would work. But now that I do, I also know why it works. And therefore, how to stop it.

It works because make<T> as you declared it is an aggregate. Even though it has a deleted default constructor, C++ still considers it an aggregate. And if you use braced-init-lists on an aggregate, you get aggregate initialization.

The way to stop it is simple: make it no longer an aggregate:

template<class T1>
struct make{
    template<class T2>
    static T1 from(T2 const& t2){
        return T1{}; //or something more complicated
    }

    make() = delete;

private:
    char c; //Not an aggregate
};

That private member forces make<T> to no longer be an aggregate. Therefore, it cannot be used with aggregate initialization, so {} will attempt to call the default constructor. Which naturally will fail since it's deleted.

Trivially copyable gymnastics might still be used to create instances of make<T>. You could shut those down by giving it a virtual destructor:

template<class T1>
struct make{
    template<class T2>
    static T1 from(T2 const& t2){
        return T1{}; //or something more complicated
    }

    make() = delete;

private:
    char c; //Not an aggregate
    virtual ~make() = default;
};

That ought to be sufficient to prevent legal C++ code from creating an object of that type.

Community
  • 1
  • 1
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    Good insight, but with your code this is still allowed: `auto p = new make;`, it seems that one needs still to delete the constructor (or make it private). – alfC Aug 25 '16 at 00:08
  • @alfC: I meant to include deleting the construct. It's just a copy-and-paste error. – Nicol Bolas Aug 25 '16 at 00:56
  • ok, what do you gain by making it a non-agregate once the constructor and the destructors are deleted or declared private? – alfC Aug 25 '16 at 04:04
  • @alfC: I explained what you gained. The fact that it's an aggregate is what allowed `make{}` to work even though `make()` did not. By deleting the constructor, you prevent the latter. By making it not an aggregate, you prevent the former. By doing *both*, you prevent all forms of initialization that do not require a pre-existing object of that type (ie: copying/moving). – Nicol Bolas Aug 25 '16 at 04:12
  • I mean that if delete both the constructor and the destructor, also myself forbid `make{}`, no? – alfC Aug 25 '16 at 06:49
  • 1
    @alfC: No. So long as aggregate initialization is possible, then so too is [`new make{}`](http://rextester.com/LNOD71364). Note that the version of GCC in that live demonstration disagrees, but the standard is *very* clear on this matter. Both Clang and VS compile that just fine. Also, if you delete the destructor, you get a strange and unexpected error. That is, it claims the problem is the deleted destructor. Which it is, but the error the user would expect is the deleted *constructor*. So by making it not an aggregate, you also improve the error messages. – Nicol Bolas Aug 25 '16 at 13:26
  • ok, so my online compiler was giving me a false impression. One needs to 1) delete constructor, 2) making it non-agregate, 3) private the virtual destructor (perhaps deleting the non-virtual contructor works equally well). Perhaps I can also make something `final` to avoid inheritance as well. Perhaps once can make a base class `noninstantiable` like we used to have `boost::noncopyable`. – alfC Aug 25 '16 at 22:07
  • 1
    @alfC: "*Perhaps I can also make something final to avoid inheritance as well.*" That's pointless, since even if you do make something a base class, you still have to *construct* your base classes. As for making some `noninstantiable`... generally speaking, most people really don't care if you instantiate a class that only contains static members. Doing that doesn't *break* anything. Making a class `noncopyable` was useful since it prevented you from doing something that the class couldn't do. – Nicol Bolas Aug 25 '16 at 22:58
  • `noninstantatiable` would make it more explicit. But I agree that what I am doing could be pointless, and there maybe uses for having instances. The class type can be passed as argument to a function, and that can be useful in itself. `template f(double, Maker){...}` and then `f(5., make{})`. – alfC Aug 25 '16 at 23:20