5

I have a custom class that I want to behave like a built-in type.

However I have noticed that you can initialise a const variable of that class without providing an initial value. My class currently has an empty default constructor.

Here is a comparison of int and my class foo:

int a;              // Valid
int a = 1;          // Valid
const int a = 1;    // Valid
const int a;        // Error

foo a;              // Valid
foo a = 1;          // Valid
const foo a = 1;    // Valid
const foo a;        // Should cause an error, but it compiles

As you can see I need to prevent

const foo a;

from compiling.

Any ideas from C++ gurus?

ljbade
  • 4,576
  • 4
  • 30
  • 35

2 Answers2

6

It compiles only if it has a default constructor, and it compiles because it has it, which means that it is initialized. If you don't want that line to compile, just disable the default constructor (will also make foo a; an error as an unwanted side effect). Without a definition of foo or what you want to do, this is as far as I can get.

I don't think there is any way of achieving what you want (i.e. allow the non-const variable to be default initialized, while having the const version fail compilation and allowing the other use cases --that require providing constructors)

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • You could make the default constructor protected. – arne Nov 18 '11 at 12:24
  • 3
    @arne: I don't see how that would help. Either the default constructor is accessible and both `foo a; const foo a;` compile, or it is not and neither compiles. Additionally, `protected` would probably not be the best approach, since there is no inheritance in the question, the natural choices would be `public` to grant access and `private` to forbid it. – David Rodríguez - dribeas Nov 18 '11 at 12:27
  • @arne: That would achieve what? `protected` visibility is only useful in inheritance scenarios. – Xeo Nov 18 '11 at 12:27
3

The rules of C++ simply say that default-initialization (e.g. new T;) and value-initialization (e.g. new T();) are the same for objects of class type, but not for objects of fundamental type.

There's nothing you can do to "override" this distinction. It's a fundamental part of the grammar. If your class is value-initializable, then it is also default-initializable.

There is a sort-of exception for classes without any user-defined constructors: In that case, initialization of members is done recursively (so if you default-init the object, it tries to default-init all members), and this will fail if any of the class members are themselves fundamental, or again of this nature.

For example, consider the following two classes:

struct Foo { int a; int b; };
struct Goo { int a; int b; Goo(){} };

//const Foo x; // error
const Goo y;   // OK

The implicit constructor for Foo is rejected because it doesn't initialize the fundamental members. However, y is happily default-initialized, and y.a and y.b are now "intentionally left blank".

But unless your class doesn't have any user-defined constructors, this information won't help you. You cannot "forward" the initialization type to a member (like Foo() : INIT_SAME_AS_SELF(a), b() { }).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • In the first sentence, *fundamental type* should be changed to *POD type*, which includes fundamental types and other types that fit --to some extent-- your description in the third paragraph. Also note that the third paragraph is also not precise in the description of which types fall under that category: the presence of a member of *fundamental type* does not change the behavior, but rather the *absence of a non-POD member*, and it is also missing the fact that a type with only fundamental types *and a virtual function* will also imply that `new T;` and `new T();` are equivalent... – David Rodríguez - dribeas Nov 18 '11 at 14:23
  • @DavidRodríguez-dribeas: No, POD isn't right. Your class may well have non-POD members and still behave this way. Perhaps "aggregate", but I think it's only the absence of a user *constructor*. E.g. having a destructor doesn't change this. Try adding a `std::string` member or a destructor. I think the recursive definition using the implied constructor is the most universal and concise. – Kerrek SB Nov 18 '11 at 14:25
  • @DavidRodríguez-dribeas: I don't think any of what you said is correct: Try `struct Foo { std::string a; virtual void f() { }; ~Foo() {} };` Now `const Foo x;` still doesn't work, but `const Foo x{};` does. – Kerrek SB Nov 18 '11 at 14:29
  • `struct test1 { std::string s; int i; }; const test1 t;` compiles. `struct test2 { int i; virtual void f() {} }; const test2 t;` compiles. You are right in that destructors do not affect the problem, so POD is not the best definition, but your definitions above aren't either. The example that you provide in your last comment compiles fine for me (g++4.6, c++03 mode) and for ideone in both c++ and c++11 modes. What compiler have you tried? – David Rodríguez - dribeas Nov 18 '11 at 19:28
  • @DavidRodríguez-dribeas: That's GCC 4.6.2, with or without `-std=c++0x`. – Kerrek SB Nov 18 '11 at 19:30
  • Sorry, I had not tried with g++4.6, but with g++-4.2 (default in MacOSX snow leopard). Clang++ 3.0 agrees with g++-4.6, so it might be an issue with older g++ implementations, I would have to go back to the standard to check – David Rodríguez - dribeas Nov 18 '11 at 19:47
  • @DavidRodríguez-dribeas: Please do. I propose a simple 3-step rule: 1) fundamental type, 2) array or class without ud-constructor, 3) class with constructor. (1) is familiar; (2) applies recursively to members, and (3) is default=value. – Kerrek SB Nov 18 '11 at 19:50
  • Wouldn't it be as simple as you are allowed to create a constant of a user defined type (or array of that type) with *default-initialization* only if the type has a user defined constructor?* (To be honest, I don't quite understand the rule that you are proposing... 1 and 2 require explicit initialization, while 3 doesn't, or am I missing something?) – David Rodríguez - dribeas Nov 18 '11 at 20:05
  • (1) says that default-init is different from value-init. (2) says that you apply the entire rule recursively to each member. Your proposed rule is possibly OK, with the additional need to mention arrays. – Kerrek SB Nov 18 '11 at 20:06