There are two main ways to declare and initialise variables with automatic or static storage in C++: with `='
; and with parentheses.
Some C++ programmers prefer to use copyconstructor-style initialisation syntax for clarity because it is the copy constructor that's invoked in both cases, not the assignment operator as one might expect from the use of the equals sign.
But I think clarity is the complete opposite of what you get. It can cause problems where you do not expect them, leading to complicated diagnostics:
// error: undefined reference to 'x(int (*)())'
Although the linker error makes sense if you know that you have declared a function and never defined it, the diagnostic does not really help you fix the problem if you did not see this coming and expected x
to be equal to 0.
"Wait, what. I've declared a function?"
Yep! The only difference between a function declaration int(T)
and a temporary construction int(x)
is that T
is a type whereas x
is an expression. So:
// initialises b to value of temporary int 3
int b = int(3);
// initialises c to value of temporary int 3
int c(int(3));
// fail: declares a function d taking a {function
// taking a T and returning int} and returning int
int d(int(T));
// fail: declares a function e taking a {nullary
// function returning int} and returning int
int e(int());
In the last case above, because the int()
has nothing inside the parentheses it can be parsed as either, and function declaration is always assumed in C++ where it's possible.
There are plenty of cases where a type simply cannot be in a situation like this, and in the other cases you can hack around it by forcing the declaration to become an expression with extra parentheses:
Still, even if just because of this, I avoid "int x(3);
" and instead prefer good-old equals-syntax which, although it doesn't actually invoke the assignment operator, at least does what it says it does: it declares and initialises an integer variable called x
, equal to 3:
Stick to this syntax and you won't have any trouble; problem solved. 🙂
Further reading
Non-type Lookup
During a discussion about the validity of A::A
(and the fact that you cannot pass explicit template parameters to a template constructor) it came up that although non-types are ignored in the lookup for a base-clause, the same is not true for typedef… even though clearly only types are to be expected in either case.
Observe:
typedef A lulz;
// this is illformed; void A() is assumed in the typedef.
// error: 'A' does not name a type
But:
struct B : A {};
// valid; non-types are ignored during lookup
Stupid language.
Constructor template parameters
This discussion had come about from a debate over the technique displayed in the following example ("BARK" is a third-party preprocessor macro that outputs the signature of the function that it's in):
int main() {
A a(3);
}
// output: A(const int&)
Using the template constructor in this way is valid because the template parameter is deduced to be int from the function argument 3.
Now, let's look at the case where the class itself is the template. The class's template parameters cannot be deduced from arguments to its constructor, alas (at least not until C++0x):
int main() {
A a(3);
}
// error: invalid use of template-name 'A' without an
// argument list
However you can, of course, specify the template class parameter explicitly:
int main() {
A a(3);
}
// output: A(const int&) A(const int&)
You'd expect that specifying a template parameter explicitly is always valid, and that only when trying to rely on implicit deduction might things get a bit hairy.
But you actually can't specify a template constructor parameter explicitly at all. The interesting thing here is in GCC's diagnostic, which isn't quite what you might expect:
int main() {
A::A(3);
}
// GCC error: A is not a template type
What is happening here is that any class A
is brought into its own scope. So as a type, A::A
is equivalent to A
, as is A::A::A
and A::A::A::A::A::A::A
. GCC parses A::A<int>()
as A<int>()
, then attempts to construct a temporary of type A<int>
, but class A
isn't itself a template so this fails.
A lengthy debate ensued and it was concluded that GCC is erroneous here. Comeau in fact agrees with our ultimate interpretation of the standard, and allows this by correctly parsing A::A<int>
as the constructor A<int>
of class A
… although it's still not possible to directly invoke the constructor.
I'm actually still not 100% clear on this, so please jump on the wagon in the post comments if you can shed some more light on the issue.
You may have used code such as [1]:
// Success
Here we obtain a pointer b
to the same object that a
points to, but we say that it is now immutable (through b, at least).
However, C++ does not allow a programmer to add const
ness more than one layer of indirection away [2]:
// error: invalid conversion from 'int' to 'const int'
Of course, you can still make the pointer itself const
[3]:
// Success
So it turns out that you can add const
ness to the pointer [3], or to the immediate pointee [1], but you can't add const
ness to the pointee of the pointee [2].
The question is: why not? How does adding const
ness violate const
-correctness? Answers on a postcard, please.
Update: As Kniht correctly pointed out in the comments (and, in fact, as the standard itself explains in a note), this is invalid for very good reason:
include
int main() {
char* p = 0;
//char const a = &p; // not allowed, but let's pretend it is
char const a = (char const**)&p; // instead force the cast to compile
char const orig = "original";
a = orig; // type of a is char const, which is the type of orig, this is allowed
assert(p == orig); // oops! char points to a char const
}