11

Let's say I want to make a type that cannot be constructed (don't ask why).

struct Impossible
{

I could do it like this:

    Impossible() = delete;
    // disable automatically generated constructors, don't declare any others
    Impossible(const Impossible&) = delete;
    // I suppose this is redundant with nothing to copy

or like this:

    Impossible(...) = delete;
    // explicitly disable all constructors

or like this:

    template<typename... Ts>
    Impossible(Ts...) = delete;
    // explicitly disable all constructors, template version
};

I guess I could ask the same thing about any function, not just constructors.

Does it make any difference which one I choose? In terms of syntax I think I like the second option. But is there any situation, whatsoever, where it's possible to detect a difference (other than in the text of an error message)?

Sam
  • 7,252
  • 16
  • 46
  • 65
glaebhoerl
  • 7,695
  • 3
  • 30
  • 41

3 Answers3

8

The first one is more than enough - no constructors will be generated by the compiler and, most imporatantly, it's idiomatic.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
7

There is a difference, for example:

#include <type_traits>

struct Unconstructible
{
    Unconstructible() = delete;
};

static_assert( !std::is_default_constructible<Unconstructible>::value, "not default constructible" );
static_assert( std::is_copy_constructible<Unconstructible>::value, "copyable" );

Although you can never construct this object, so in practice you can never create a copy of one either, according the language and the type traits in the library, it is technically CopyConstructible, because there's an implicitly-declared copy constructor.

Similarly, with the Impossible(...) or Impossible(Ts&&...) forms there is still an implicitly-declared copy constructor.

On the other hand, if you do it this way:

#include <type_traits>

struct Unconstructible
{
    Unconstructible(const Unconstructible&) = delete;
};

static_assert( !std::is_default_constructible<Unconstructible>::value, "not default constructible" );
static_assert( !std::is_copy_constructible<Unconstructible>::value, "not copyable" );

The existence of a user-declared constructor suppresses the implicit declaration of the default constructor, so the class is neither DefaultConstructible nor CopyConstructible.


N.B. your last example should probably be Impossible(Ts&&...) to match any types, including non-copyable and non-movable ones.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Wait. Even if I explicitly declare a more-general constructor that meets the requirements for being a copy constructor (it accepts an argument of the self-type), and delete it, the compiler will still generate /another/ copy constructor, which is a better match after considering overloading? Is that definitely right? – glaebhoerl Jan 03 '13 at 16:22
  • 2
    Yes. [class.copy]/2 _A non-template constructor for class `X` is a copy constructor if its first parameter is of type `X&`, `const X&`, `volatile X&` or `const volatile X&`, and either there are no other parameters or else all other parameters have default arguments._ [class.copy]/7 _If the class definition does not explicitly declare a copy constructor, one is declared implicitly._ So the more-general constructor is not technically a "copy constructor" even though it can be used to copy the type. – Jonathan Wakely Jan 03 '13 at 16:26
  • That's good to know. Thanks! (And mea culpa regarding `Ts...`. For some reason I was thinking that the types would be deduced to be *anything* (reference, rvalue reference, whatever) as necessary depending on what was passed, but upon reflection that's obviously dumb. Just because it's a parameter *pack* won't make it magically behave differently from single parameters. (As opposed to rvalue references to template parameters which *do* behave magically in a similar sort of way to enable perfect forwarding, as you said.)) – glaebhoerl Jan 03 '13 at 16:32
5

You cannot create instance of this class:

struct Impossible
{
    Impossible() = delete;
};

Note the question of copy-constructor (or move-constructor) doesn't even arise, so deleting them is unnecessary. An object cannot exist because it cannot be constructed, so the copying or moving is out of question.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Yep, as I mentioned. But I'm curious about the "any possible way to detect" part of it. Is there _any_ code you can write that will behave differently depending on which style I chose? Say, using SFINAE? – glaebhoerl Jan 03 '13 at 12:03