C++17 introduces template argument deduction.
With gcc-7.2, I can use it easily in a function:
int test() {
std::pair d(0, 0.0);
}
I was expecting this same syntax to work in class non-static data members, like:
class Test {
std::pair d_{0, 0.0};
};
but this causes gcc error: invalid use of template-name ... without an argument list, with --std=c++17 passed.
I tried a few other combinations, but none seems to work.
Is this the intended behavior by the standard, or is this a case of incomplete support by the compiler? I can't find any explicit reference to class data members in the standard.
My use case is of course much more complex, having this syntax would be extremely convenient (think functions being passed and stored).
Is this the intended behavior by the standard, or is this a case of incomplete support by the compiler?
Yes, this is intended behavior. [dcl.type.class.deduct] reads:
If a placeholder for a deduced class type appears as a decl-specifier in the decl-specifier-seq of an initializing declaration ([dcl.init]) of a variable, [...]
A placeholder for a deduced class type can also be used in the type-specifier-seq in the new-type-id or type-id of a new-expression, or as the simple-type-specifier in an explicit type conversion (functional notation). A placeholder for a deduced class type shall not appear in any other context.
A non-static data member is not a variable, and we're in none of the other situations.
Note that the same principle is true for non-static data members attempting to be declared with auto:
struct X {
auto y = 0; // error
};
The default member initializer is just that - a default initializer. What if you provided a constructor that initialized the member with expression(s) of different types?
Related
Please consider the following code:
template <typename T>
struct Test
{
Test() = default;
explicit Test(const T& arg)
: m_member(arg)
{
}
T m_member{};
};
int main()
{
Test<int> t1;
int v2 = 34;
Test<int&> t2(v2); // (!)
return 0;
}
Should the code above compile and exhibit no undefined behavior?
The line marked (!) instantiates the class template Test with a parameter of reference type. In this case, the default initializer for member Test::m_member is invalid (well, a reference must be initialized with some object). But on the other hand, the default constructor (the only one which could have used that default initializer) is never used in the program, and so it shouldn't be instantiated.
Under C++11, is the compiler allowed/required to attempt/skip the instantiation of a default initializer for a member which is not used in the instantiated constructors (i.e. the program doesn't instantiate any of the constructors which could require that initializer)?
Fortunately, this is OK.
But on the other hand, the default constructor (the only one which could have used that default initializer) is never used in the program, and so it shouldn't be instantiated.
Indeed:
[temp.mem.func]/2
The template-arguments for a member function of a class template are determined by the template-arguments of the type of the object for which the member function is called.
Since in your example, Test<int&>::Test() is not called, it's template-arguments are not determined and its construct doesn't make the program ill-formed.
This is described in CWG Issue 1396
Deferred instantiation and checking of non-static data member initializers
Section: 17.8.1 [temp.inst] Status: drafting Submitter: Jason Merrill Date: 2011-09-22
Non-static data member initializers get the same late parsing as
member functions and default arguments, but are they also instantiated
as needed like them? And when is their validity checked?
Notes from the October, 2012 meeting:
CWG agreed that non-static data member initializers should be handled
like default arguments.
As you see, the consensus was that default member initializers be treated like default arguments to functions. So we can examine the behavior for default arguments to determine how the CWG intends for this to be treated. We can see that those aren't instantiated along with the class definition:
[temp.inst]/1
The implicit instantiation of a class template specialization causes
the implicit instantiation of the declarations, but not of the
definitions or default arguments, of the class member functions,
member classes, scoped member enumerations, static data members and
member templates;
And that they would be instantiated if a constructor was called in a way that used them, same as a member function f in this paragraph
[temp.inst]/12
If a function template f is called in a way that requires a default
argument to be used, the dependent names are looked up, the semantics
constraints are checked, and the instantiation of any template used in
the default argument is done as if the default argument had been an
initializer used in a function template specialization with the same
scope, the same template parameters and the same access as that of the
function template f used at that point. This analysis is called
default argument instantiation. The instantiated default argument is
then used as the argument of f.
Should the code above compile and exhibit no undefined behavior?
Yes, I can't see any reason why it shouldn't.
Under C++11, is the compiler allowed/required to attempt/skip the
instantiation of a default initializer for a member which is not used
in the instantiated constructors (i.e. the program doesn't instantiate
any of the constructors which could require that initializer)?
I don't have the C++11 spec but the current draft says:
[default.ctor-2.3]
A defaulted default constructor for class X is defined as deleted if:
any non-static data member with no default member initializer ([class.mem]) is of reference type,
Since you do have a default member initializer (T m_member{};) it will not delete your defaulted default constructor in case you try to use it when T is a reference type.
It'll instead fail compiling:
All output is from using -std=c++11
g++: error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’
clang++ error: non-const lvalue reference to type 'int' cannot bind to an initializer list temporary
Trying to fix it by removing the default member initializer:
11 | T m_member;
Results in the deletion required:
21:16: error: use of deleted function ‘Test<T>::Test() [with T = int&]’
21 | Test<int&> x;
| ^
4:5: note: ‘Test<T>::Test() [with T = int&]’ is implicitly deleted because the default definition would be ill-formed:
4 | Test() = default;
| ^~~~
4:5: error: uninitialized reference member in ‘struct Test<int&>’
Whoever is saying that this question is a duplicate of the question "Is there a specific reason why a trailing-return-type is not a complete-class context of a class?" does not know what he/she is talking about. The fact that a trailing return type is not a complete-class context of a class does not explain why the code in this question doesn't compile, although it explains the rejection of the code given in the answer to the other question, specially the part of the code involving the member functions qux and baz, as explained by the OP.
In order to clarify my argument that the code below is valid, you have to take into consideration the second note in [expr.prim.this] which says: In a trailing-return-type, the class being defined is not required to be complete for purposes of class member access. Class members declared later are not visible. As foo is declared before bar in my example, there is nothing to prevent the compiler from accessing foo in the trailing-return-type of bar.
Note that the comment below by #NathanOliver is based on the conjecture that the inline definition for the member function foo below is just syntactic sugar. This needs to be proven from a quote in the Standard. I haven't found that yet. Once that quote is produced, I will certainly accept an answer arguing that the code doesn't compile because a trailing-return-type is not a complete-class context of a class.
struct Test {
auto foo() {}
auto bar() -> decltype(foo()) {}
};
prog.cc:3:32: error: use of 'auto Test::foo()' before deduction of 'auto'
3 | auto bar() -> decltype(foo()) {}
| ^
prog.cc:3:32: error: use of 'auto Test::foo()' before deduction of 'auto'
[dcl.spec.auto]/9:
If a function with a declared return type that uses a placeholder type
has no non-discarded return statements, the return type is deduced as
though from a return statement with no operand at the closing brace of
the function body. [ Example:
auto f() { } // OK, return type is void
auto* g() { } // error, cannot deduce auto* from void()
— end example ]
[dcl.type.auto.deduct]/(2.1):
A type T containing a placeholder type, and a corresponding
initializer e, are determined as follows:
(2.1) for a non-discarded
return statement that occurs in a function declared with a return type
that contains a placeholder type, T is the declared return type and e
is the operand of the return statement. If the return statement has no
operand, then e is void();
(2.2) for a variable declared with a
type that contains a placeholder type, T is the declared type of the
variable and e is the initializer. If the initialization is
direct-list-initialization, the initializer shall be a
braced-init-list containing only a single assignment-expression and e
is the assignment-expression;
(2.3) for a non-type template
parameter declared with a type that contains a placeholder type, T is
the declared type of the non-type template parameter and e is the
corresponding template argument.
According to [dcl.spec.auto]/9 and [dcl.type.auto.deduct]/(2.1) the code should compile. But GCC and cland reject it. What am I missing?
struct Test
{
auto foo() { /*1*/ }
auto bar() -> decltype(foo()) {}
};
At marker 1, the name Test::bar is in scope along with all other members of struct Test. Therefore the compiler cannot analyze the body of foo() until the class is complete.
We then have a partial ordering:
Parse body of Test::foo() before deducing its return type
Complete class Test before parsing body of Test::foo()
Analyze trailing return type of Test::bar() before completing class Test (from the question you pled is not a dupe)
and by transitivity, the return type of Test::bar() must be analyzed without yet performing return type deduction for Test::foo().
Since a Standard quote was requested, here it is from [class.mem]:
A class is considered a completely-defined object type (or complete type) at the closing } of the class-specifier. Within the class member-specification, the class is regarded as complete within function bodies, default arguments, noexcept-specifiers, and default member initializers (including such things in nested classes). Otherwise it is regarded as incomplete within its own class member-specification.
With class template argument deduction we can write:
std::less Fn;
However, G++ 8.2 rejects this code:
#include <algorithm>
#include <vector>
#include <functional>
int main()
{
std::vector v= { 1, 3, 2, 7, 5, 4 };
std::sort(v.begin(),v.end(),std::greater());
}
emitting the following error:
error: cannot deduce template arguments for 'greater' from ()
Clang++ 7.0 and MSVC 15.8.0 compile it without warnings. Which compiler is right?
GCC is wrong. There is already a bug report.
[dcl.type.simple]/2 says:
A type-specifier of the form typenameopt nested-name-specifieropt template-name is a placeholder for a deduced class type ([dcl.type.class.deduct]).
And [dcl.type.class.deduct]/2 says:
A placeholder for a deduced class type can also be used in the type-specifier-seq in the new-type-id or type-id of a new-expression, as the simple-type-specifier in an explicit type conversion (functional notation) ([expr.type.conv]), or as the type-specifier in the parameter-declaration of a template-parameter. A placeholder for a deduced class type shall not appear in any other context.
Such use is allowed.
[temp.arg]/4 describes the syntax error that a template-id is required but there is no <>. However here std::greater is not resolved as a template-id so that paragraph does not apply.
Clang and MSVC are correct. This should be well-formed because of the combination effect of implicitly-generated deduction guides (since C++17) and default template argument.
(emphasis mine)
When a function-style cast or declaration of a variable uses the name
of a primary class template C without an argument list as the type
specifier, deduction will proceed as follows:
If C is defined, for each constructor (or constructor template) Ci declared in the named primary template (if it is defined), a fictional
function template Fi, is constructed, such that
template parameters of Fi are the template parameters of C followed (if Ci is a constructor template) by the template parameters
of Ci (default template arguments are included too)
the function parameters of Fi are the constructor parameters
the return type of Fi is C followed by the template parameters of the class template enclosed in <>
If C is not defined or does not declare any constructors, an additional fictional function template is added, derived as above from
a hypothetical constructor C()
In any case, an additional fictional function template derived as above from a hypothetical constructor C(C) is added, called the copy
deduction candidate.
Template argument deduction and overload resolution is then performed
for initialization of a fictional object of hypothetical class type,
whose constructor signatures match the guides (except for return type)
for the purpose of forming an overload set, and the initializer is
provided by the context in which class template argument deduction was
performed, except that the first phase of list-initialization
(considering initializer-list constructors) is omitted if the
initializer list consists of a single expression of type (possibly
cv-qualified) U, where U is a specialization of C or a class derived
from a specialization of C.
These fictional constructors are public members of the hypothetical
class type. They are explicit if the guide was formed from an explicit
constructor. If overload resolution fails, the program is ill-formed.
Otherwise, the return type of the selected F template specialization
becomes the deduced class template specialization.
Given std::greater(), the implicitly-generated deduction guide is applied and the additional fictional function is selected at last. As the result of overload resolution the default argument void is applied, then the deduced type will be void. That means std::greater() should be same as writing std::greater<void>() or std::greater<>().
BTW: Gcc doesn't compile with std::greater(), but std::greater{} or std::greater g; are fine, it might be gcc's bug.
I would've expected an initialiser would only be necessary for the first declaration. e.g.
auto x = 2, y;
I would expect this to deduce x's type as int and then implicitly replace "auto" with the base type "int", meaning y would then be a default initialised integer. Actually the entire thing doesn't compile because y explicitly needs and initialiser. Similarly it's odd to me that
auto x = 2, y = 3.3;
causes an error too. I would've expected y to be initialsed to 3 in a double-to-int conversion, but:
error: inconsistent deduction for 'auto': 'int' and then 'double'
I read through http://en.cppreference.com/w/cpp/language/auto and couldn't explicitly find an explanation. Actually it seemed like that link was on my side:
Once the type of the initializer has been determined, the compiler determines the type that will replace the keyword auto using the rules for template argument deduction from a function call (see template argument deduction#Other contexts for details).
Is it simply "just cause"?
Is it simply "just cause"?
Yes.
Both variables have a deduced type, and both variables thus need an initialiser. The logic that requires both to have the same type is applied post-deduction.
[C++11: 7.1.6.4/7]: If the list of declarators contains more than one declarator, the type of each declared variable is determined as described above. If the type deduced for the template parameter U is not the same in each deduction, the program is ill-formed.
[C++14: 7.1.6.4/8]: If the init-declarator-list contains more than one init-declarator, they shall all form declarations of variables. The type of each declared variable is determined as described above, and if the type that replaces the placeholder type is not the same in each deduction, the program is ill-formed.
Call it a C++ oddity, but I imagine it's there to help keep the standard wording simple. After all, wouldn't it be a little confusing (and by that I mean more confusing/unclear than auto already is) if your example worked as you describe?
I recently found out that the types of parameters in a non-defining function declaration may be of incomplete types. This is very exciting.
class A;
class B {
B(A a); // Legal! Wow!
};
The type is required to be complete only for the definition:
B::B(A a) {}; // error: ‘a’ has incomplete type
I've been trying to pin down the legalese for this, but my searches through C++11 for "[in]complete type" have yielded nothing of much interest, leading me to assume that these semantics are defined through an enigmatic maze of constructions.
Can you help me pin down the standard text that defines the above requirements for the types of function parameters being complete or otherwise, in function declarations vs definitions?
(9.2/10 and 9.4.2/2 give us the requirements for static data member declarations and non-static data member definitions in class definitions.)
See 8.3.5p9, which lays down the exact rules. For a = delete definition, implementations are likely to accept incomplete parameter types too, retroactively (as was determined in a DR resolution by the C++ committee).
In particular, there is no action done on parameters or return values in a non-defining function declaration. Copying of arguments to parameters is done in the context of the caller. And destruction of parameters is done in the context of the callee, in the function definition. Destruction of the return value is done in the context of the caller in a function call except if the call is the topmost expression or right operand of a topmost comma operator in a decltype. Then no destruction happens because no temporary is created as a special case (to help SFINAE libraries).
Function declaration
There doesn't appear to be anything directly addressing this. It may be that it's allowed because it is not disallowed.
7.1.1/9 tells us that it's ok for an extern declaration (which is semantically similar to a member function declaration), and shows us non-normatively that types in such declarations may be incomplete:
[C++11: 7.1.1/9]: The name of a declared but undefined class can be used in an extern declaration. Such a declaration can only be used in ways that do not require a complete class type. [ Example:
struct S;
extern S a;
extern S f();
extern void g(S);
void h() {
g(a); // error: S is incomplete
f(); // error: S is incomplete
}
—end example ]
Function definition (thanks litb)
[C++11: 8.3.5/9]: Types shall not be defined in return or parameter types. The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function definition is nested within the member-specification for that class (including definitions in nested classes defined within
the class).
Function call
[C++11: 5.2.2/4]: [..] When a function is called, the parameters that have object type shall have completely-defined object type. [ Note: this still allows a parameter to be a pointer or reference to an incomplete class type. However, it prevents a passed-by-value parameter to have an incomplete class type. —end note ] [..]