Can class template constructors have a redundant template parameter list in c++20
There was a change, in fact. It's documented in the compatibility section of the C++20 draft.
[diff.cpp17.class]
2 Affected subclauses: [class.ctor] and [class.dtor]
Change: A simple-template-id is no longer valid as the declarator-id of a constructor or destructor.
Rationale: Remove potentially error-prone option for redundancy.
Effect on original feature: Valid C++ 2017 code may fail to compile in this International Standard. For example:template<class T>
struct A {
A<T>(); // error: simple-template-id not allowed for constructor
A(int); // OK, injected-class-name used
~A<T>(); // error: simple-template-id not allowed for destructor
};
Specifically, the wording delta is this:
n4659 - C++17 standard draft - [class.ctor]
1 Constructors do not have names. In a declaration of a constructor,
the declarator is a function declarator of the formptr-declarator ( parameter-declaration-clause ) noexcept-specifier attribute-specifier-seq
where the ptr-declarator consists solely of an id-expression, an optional attribute-specifier-seq, and optional surrounding parentheses, and the id-expression has one of the following forms:
- in a member-declaration that belongs to the member-specification of a class but is not a friend declaration, the id-expression is the injected-class-name of the immediately-enclosing class;
- in a member-declaration that belongs to the member-specification of a class template but is not a friend declaration, the id-expression is
a class-name that names the current instantiation of the
immediately-enclosing class template; orn4861 - C++20 standard draft - [class.ctor]
1 A constructor is introduced by a declaration whose declarator is a
function declarator ([dcl.fct]) of the formptr-declarator ( parameter-declaration-clause ) noexcept-specifier attribute-specifier-seq
where the ptr-declarator consists solely of an id-expression, an
optional attribute-specifier-seq, and optional surrounding
parentheses, and the id-expression has one of the following forms:
- in a member-declaration that belongs to the member-specification of a class or class template but is not a friend declaration
([class.friend]), the id-expression is the injected-class-name
([class.pre]) of the immediately-enclosing entity or
As you can see, the wording changed. C++20 now requires the injected class name when declaring a constructor for a class template. S<T>
is a simple template id that names a specialization. Inside a template, the injected class name is simply S
.
This is part of addressing CWG 2237.
class template's constructor declaration doesn't compile for C++20 but compiles for C++17
It is not a bug.
It is the consequence of a change in the standard.
Affected subclauses: [class.ctor] and [class.dtor]
Change: A simple-template-id is no longer valid as the declarator-id of a constructor or destructor.
Rationale: Remove potentially error-prone option for redundancy.
Effect on original feature: Valid C++ 2017 code may fail to compile in this revision of C++. For example:
template<class T>
struct A {
A<T>(); // error: simple-template-id not allowed for constructor
A(int); // OK, injected-class-name used
~A<T>(); // error: simple-template-id not allowed for destructor
};
There is however a bug report, that was initially closed and then reopen with the intention of improving the diagnostics message.
how to do template class constructors as forward declaration
It's really not different from any other type of forward declaration. Just write:
template <typename _ChClass, typename _ChFunction>
class CChMyClass: public CChMyBaseClass {
...
CChMyClass() requires (std::is_member_function_pointer<_ChFunction>::value);
CChMyClass() requires (!std::is_member_function_pointer<_ChFunction>::value);
...
};
I think the difficulty is rather in the actual function definitions. But even that is not too difficult if you think about it. You just need to include the template
parameters and the requires
clause again:
template <typename _ChClass, typename _ChFunction>
CChMyClass<_ChClass, _ChFunction>::CChMyClass()
requires (std::is_member_function_pointer<_ChFunction>::value)
: CChMyBaseClass(), m_ChFunction(...) {
...
};
Note that in your example, you did not declare two instances of CChMyClass
in main()
, but rather forward declared two functions that had a CChMyClass
as a return type (this is known as the most vexing parse). Just omit the ()
at the end, or use braces instead.
Why not infer template parameter from constructor?
I think it is not valid because the constructor isn't always the only point of entry of the class (I am talking about copy constructor and operator=). So suppose you are using your class like this :
MyClass m(string s);
MyClass *pm;
*pm = m;
I am not sure if it would be so obvious for the parser to know what template type is the MyClass pm;
Not sure if what I said make sense but feel free to add some comment, that's an interesting question.
C++ 17
It is accepted that C++17 will have type deduction from constructor arguments.
Examples:
std::pair p(2, 4.5);
std::tuple t(4, 3, 2.5);
Accepted paper.
Initializing an array in a object in C++
The constructor you have implemented expects an int*
pointer as input, but {2, 3, 4}
does not decay to an int*
, so no, the syntax you have shown will not work given the class you have implemented so far.
If you know the exact array size at compile-time, you could do this using std::array
instead:
#include <array>
template<typename T, size_t N>
struct ary {
std::array<T, N> data;
...
};
#include "ary.h"
int main()
{
ary<int, 3> array_int1{2, 3, 4};
ary<int, 3> array_int2 = {2, 3, 4};
ary<int, 3> array_int3 = {{2, 3, 4}};
return 0;
}
Otherwise, if you really want ary
to have a pointer to some array data, you could declare an actual array first, and then pass it in to the constructor, eg:
template<typename T>
class ary {
private:
T *dataptr;
...
public:
ary(T* ptr) : dataptr(ptr) {}
...
};
#include "ary.h"
int main()
{
int data[] = {2, 3, 4};
ary<int> array_int(data);
...
return 0;
};
But consider giving your class a constructor that takes a std::initializer_list
as input instead, and then have the class allocate its own array internally (just be sure to follow the Rule of 3/5/0), eg:
#include <algorithm>
#include <utility>
template<typename T>
class ary {
private:
T *dataptr = nullptr;
int datasize = 0;
public:
ary() = default;
ary(const ary &src)
: ary()
{
if (src.dataptr && src.datasize > 0)
{
dataptr = new T[size];
datasize = src.datasize;
std::copy(src.dataptr, src.dataptr+src.datasize, dataptr);
}
}
ary(ary &&src)
: dataptr(src.dataptr), datasize(src.datasize)
{
src.dataptr = nullptr;
src.datasize = 0;
}
ary(T* ptr, int size)
: dataptr(new T[size]), datasize(size)
{
std::copy(ptr, ptr+size, dataptr);
}
ary(std::initializer_list<T> l)
: dataptr(new T[l.size()]), datasize(l.size())
{
std::copy(l.begin(), l.end(), dataptr);
}
~ary()
{
delete[] dataptr;
}
ary& operator=(ary rhs)
{
std::swap(dataptr, rhs.dataptr);
std::swap(datasize, rhs.datasize);
return *this;
}
...
};
#include "ary.h"
int main()
{
ary<int> array_int1;
ary<int> array_int2 = {2, 3, 4};
int data[] = {2, 3, 4};
ary<int> array_int3{data, 3};
ary<int> array_int4{array_int2};
ary<int> array_int5{std::move(array_int3)};
...
return 0;
}
A better option is to use std::vector
instead, and let it do all the work for you, eg:
#include <vector>
template<typename T>
class ary {
private:
std::vector<T> data;
public:
ary() = default;
// the copy/move constructors, copy/move assignment operators,
// and destructor will be implicitly generated for you...
ary(T* ptr, int size) : data(ptr, size) {}
ary(std::initializer_list<T> l) : data(l) {}
...
};
#include "ary.h"
int main()
{
ary<int> array_int1;
ary<int> array_int2 = {2, 3, 4};
int data[] = {2, 3, 4};
ary<int> array_int3{data, 3};
ary<int> array_int4{array_int2};
ary<int> array_int5{std::move(array_int3)};
...
return 0;
}
Related Topics
Why Is Std::Fill(0) Slower Than Std::Fill(1)
Difference Between Long and Int Data Types
Why Does the Standard Differentiate Between Direct-List-Initialization and Copy-List-Initialization
Opencv Cv::Mat and Eigen::Matrix
C++ - Arguments for Exceptions Over Return Codes
Could You Recommend Some Guides About Epoll on Linux
Reduce Flicker with Gdi+ and C++
Get the Status of a Std::Future
Scale and Rotation Template Matching
Function in C++ Returns by Value or by Reference
Could I Ever Want to Access the Address Zero
Why Does C++ Code Missing a Formal Argument Name in a Function Definition Compile Without Warnings
Easy Way Find Uninitialized Member Variables
Accessing Protected Members of Superclass in C++ with Templates