Feb 9, 2022

[C++] CTAD tips

Reference:

Deduction guide sounds very reasonable for any class template that has a constructor that takes an object of its template parameter by reference.

e.g.
template<typename T>
struct C {
     C(const T&) {
     }
};

C x{"hello"}; // T deduced as char[6]

// with deduction guide
template<typename T> C(T) -> C<T>;

C x{"hello"}; // T deduced as const char*


Non-Template Deduction Guides

template<typename T>
struct S {
	T val;
};

S(const char*) -> S<std::string>; // map S<> for string literals to S<std::string>

S s1{"hello"}; // OK, same as: S<std::string> s1{"hello"};
S s2 = {"hello"}; // OK, same as: S<std::string> s2 = {"hello"};
S s3 = S{"hello"}; // OK, both S deduced to be S<std::string>

S s4 = "hello"; // ERROR: can’t initialize aggregates without braces
S s5("hello"); // ERROR: can’t initialize aggregates without braces


Deduction Guides versus Constructors

Deduction guides compete with the constructors of a class. 
Class template argument deduction uses the constructor/guide that has the highest priority according to overload resolution. 
If a constructor and a deduction guide match equally well, the deduction guide is preferred.

template<typename T>
struct C1 {
     C1(const T&) {}
};

C1(int) -> C1<long>;

// T deduced as long; the deduction guide is used because it is
// preferred by overload resolution.
C1 x1{42}; 

// T deduced as char; the constructor is a better match (because
// no type conversion is necessary)
C1 x3{'x'};


Explicit Deduction Guides

template<typename T>
struct S {
	T val;
};

explicit S(const char*) -> S<std::string>;

S s1 = {"hello"}; // ERROR (deduction guide ignored and otherwise invalid)

S s2{"hello"}; // OK, same as: S s2{"hello"};
S s3 = S{"hello"}; // OK
S s4 = {S{"hello"}}; // OK

template<typename T>
struct Ptr {
     Ptr(T) { std::cout << "Ptr(T)\n"; }
     template<typename U>
     Ptr(U) { std::cout << "Ptr(U)\n"; }
};


template<typename T>
explicit Ptr(T) -> Ptr<T*>;

Ptr p1{42}; // deduces Ptr due to deduction guide
Ptr p2 = 42; // deduces Ptr due to constructor
int i = 42;
Ptr p3{&i}; // deduces Ptr due to deduction guide
Ptr p4 = &i; // deduces Ptr due to constructor


Deduction Guides for Aggregates

template<typename T>
struct A {
	T val;
};

A i1{42}; // ERROR
A s1("hi"); // ERROR
A s2{"hi"}; // ERROR
A s3 = "hi"; // ERROR
A s4 = {"hi"}; // ERROR

// dedution guide
A(const char*) -> A<std::string>;

A s2{"hi"}; // OK
A s4 = {"hi"}; // OK
Note that (as usual for aggregate initialization) you still need curly braces. 
Otherwise, type T is successfully deduced but the initialization is an error:

A s1("hi"); // ERROR: T is string, but no aggregate initialization 
A s3 = "hi"; // ERROR: T is string, but no aggregate initialization
In any case, for a type with complicated constructors such as std::vector<> and other STL containers, it is highly recommended not to use class template argument deduction and instead, to specify the element type(s) explicitly.

std::vector v3{"hi", "world"}; // OK, deduces std::vector 
std::vector v4("hi", "world"); // OOPS: fatal runtime error

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.