Can Class Template Constructors Have a Redundant Template Parameter List in C++20

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 form

ptr-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; or

n4861 - C++20 standard draft - [class.ctor]

1 A constructor is introduced by a declaration whose declarator is a
function declarator ([dcl.fct]) of the form

ptr-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



Leave a reply



Submit