Showing posts with label cpp17_ctad. Show all posts
Showing posts with label cpp17_ctad. Show all posts

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 due to explicit.
Ptr p2 = 42; // deduces Ptr due to constructor
int i = 42;
Ptr p3{&i}; // deduces Ptr due to deduction guide due to explicit.
Ptr p4 = &i; // deduces Ptr due to constructor


Deduction Guides for Aggregates

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

A i1{42}; // ok
A s1("hi"); // ok
A s2{"hi"}; // ok
A s4 = {"hi"}; // ok
A s3 = "hi"; // ERROR; `no viable conversion from 'const char[3]' to 'A'

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



Jan 4, 2019

[C++] ~

First, we have template.
Then, lambda expression.
Then, structured binding. (Sorry, jumped over variable template)
Then, we have class template argument deduction(CTAD)

Who don't like generic programming (Sean Parent)?

Compiler is busy writing code :-)

Dec 1, 2016

[cpp] template type deduction.

Reference:
Template Normal Programming, Part 1 - Arthur O'Dwyer - CppCon 2016.pdf
Template Normal Programming, Part 2 - Arthur O'Dwyer - CppCon 2016.pdf




Type deduction in a nutshell: 

Any template parameters that were explicitly specified by the caller
are fixed as whatever the caller said they were;
they don’t participate any further in deduction.

Each function parameter may contribute (or not) to the deduction of
each remaining template parameter (or not).

Deductions are carried out in parallel; they don’t cross-talk with each other.

At the end of this process, the compiler checks to make sure that each
template parameter (that wasn’t specified by the caller) has been
deduced at least once and that all deductions agree with each other.

Furthermore, any function parameter that does contribute to deduction must match its function argument type exactly.

AWARE: (reference: https://en.cppreference.com/w/cpp/language/template_argument_deduction)
Deducing T& (not T&&):
The compiler basically tries to deduce T by stripping all the reference
qualifiers (but not the cv-qualifiers) off of the argument type, and then reintroducing a single '&' qualifier if it helps make a match via reference collapsing. 
(for l-value; if it's r-value, T as it is.)

N4606: “...a standard conversion sequence cannot be formed if it requires binding an lvalue reference other than a reference to a non-volatile const type to an rvalue or binding an rvalue reference to an lvalue...”

In other words: You can bind a parameter of “non-volatile const lvalue reference” type to an argument of “rvalue” type.
However, this rule doesn’t interact with the type deduction rules!
The compiler won’t insert const qualifiers into its deduced T just to make the binding come out right.
-
template<typename T>
void f(T& t)
{
puts(__PRETTY_FUNCTION__);
}

int main() {
int i = 42;
f(static_cast<int&>(i));  //  [with T=int]
f(static_cast<int&&>(i));  // ERROR (T=int)
f(static_cast<const int&>(i)); //   [with T=const int]
f(static_cast<const int&&>(i));  // [with  T=const int] (!)
}
-

Lambda:
If you absolutely need the function-pointer conversion to happen,
add a unary +. (Except on MSVC.)
i.e:
msvc:
https://blogs.msdn.microsoft.com/oldnewthing/20150220-00/?p=44623
-
template<typename R, typename A>
void foo(R (*fptr)(A))
{
puts(__PRETTY_FUNCTION__);
}

int main()
{
foo( [](double x) { return int(x); } );  // error
foo( +[](double x) { return int(x); } );  // compiles 
}
-
void ContainingClass::SomeMethod()
{
 class AnonymousClass$0
 {
 public:
  AnonymousClass$0()  { }
  operator int (__cdecl *)(int k) { return cdecl_static_function; }
  operator int (__stdcall *)(int k) { return stdcall_static_function; }
  operator int (__fastcall *)(int k) { return fastcall_static_function; }
  int operator(int k) { return cdecl_static_function(k); }
 private:
  static int __cdecl cdecl_static_function(int k) { return calc(k + 42); }
  static int __stdcall stdcall_static_function(int k) { return calc(k + 42); }
  static int __fastcall fastcall_static_function(int k) { return calc(k + 42); }
 };
 
 auto f = AnonymousClass$0();
 ...
}
-

Partial specialization(Variable template):
Ref: variable_template
variable template can be defined in namespace level or class level;
it self is simply a class template with static data member; the static data member is the variable template's variable.
template<typename T>
constexpr bool is_array = false;

template<typename Tp>
constexpr bool is_array<Tp[]> = true;

template<typename Tp, int N>
constexpr bool is_array<Tp[N]> = true;

template<> // this is a full specialization
constexpr bool is_array<void> = true;

-

N4606: “Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement.”


---

ODR-used (重要)

https://en.cppreference.com/w/cpp/language/definition#ODR-use

ODR-use Informally
an object is odr-used if
  • its value is read (unless it is a compile time constant) 
  • or written, 
  • its address is taken, 
  • or a reference is bound to it; 
a reference is odr-used if
  • it is used and
  • its referent is not known at compile time; 
a function is odr-used if
  • a function call to it is made
  • or its address is taken. 
If an object, a reference or a function is odr-used
  • its definition must exist somewhere in the program; 
a violation of that is usually a link-time error.

---

N4606: “An inline function or variable shall be defined in every translation unit in which it is odr-used outside of a discarded statement.”

Inline variable: 
ref:
http://en.cppreference.com/w/cpp/language/inline

N4606: “A function template, member function of a class template, variable template, or static data member of a class template shall be defined in every translation unit in which it is implicitly instantiated, unless the corresponding specialization is explicitly instantiated in some translation unit.”

 --

“Discarded statement” is C++17ese for “untaken branch of an if-constexpr”.
Ref:
http://en.cppreference.com/w/cpp/language/if
--
Constexpr If The statement that begins with if constexpr is known as the constexpr if statement.

In a constexpr if statement, the value of condition must be a contextually converted constant expression of type bool.

If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.

The return statements in a discarded statement do not participate in function return type deduction, and if a constexpr if statement appears inside a templated entity, and if condition is not value-dependent after instantiation, the discarded statement is not instantiated when the enclosing template is instantiated 
(in particular, it can odr-use a variable that is not defined).


Difference behavior between compilers:
-
template <typename T> T vt;
// GCC: this is a definition just as much as "int v;" would be
// Clang: this is just a declaration, akin to "extern int v;"

int i = vt<int>;
// GCC/MSVC reserve space for vt<int> here;
// Clang merely sets you up for a linker error

template<class T, class... Us, class V> void f();
f<int,char,int>(); // T=int; Us=<char,int>; V cannot be deduced

template<class... Ts, class U> void g(U);
g<int,char>(3.1); // Ts=<int,char>; U=double

template<class... Ts> void h(Ts...);
h<int,char>(0, 0, 3.1); // Ts=<int,char,double>

template<class... Ts, class U> void f(U, Ts...);
f('x', 1, 2); // U=char; Ts=<int,int>

template<class T, class... Us> void g(Us... us, T);
g('x', 1, 2); // us doesn’t contribute to deduction, so this fails

template<class T, class... Us> void h(Us... us, T);
h<int,int,int>('x', 1, 2); // us doesn’t contribute to deduction, 
// but we explicitly stated T=int, Us=<int,int>, which happens to work!
-----------

C++17: 
inline variables: 
 --- template<auto> -
template<class Ty, Ty Value>
struct integral_constant {
static constexpr auto value = Value;
};

template<auto Value>
struct new_integral_constant {
static constexpr auto value = Value;
};

--
Template parameter deduction for class template constructors:
-
template<typename T>
struct myvec {
explicit myvec(T t); // constructor
};

int main() {
myvec v(1);
}

template<typename T>
struct myvec {
myvec(T t); //choose
myvec(T *p);
};

int main() {
myvec v(1);
}
--
template<typename T>
struct myvec {
myvec(T t);
myvec(T *p); //choose
};

int main() {
int i;
myvec v(&i);
 // OK in C++17
}
-
template<typename T>
struct myvec {
// But will having constructor overload error; i.e. myvec(double); myvec(double)
myvec(T t); // choose, since only by choosing this could the type T be deduced.
myvec(double d);
};
int main() {
myvec v(1.0);   // OK in C++17
}
-
template<typename T>
struct myvec {
myvec(T t);  // Choose, T = int*, int* matches below T*
};

template<typename T>
struct myvec<T*> {
myvec(T* t);  // Match here!!!
};

int main() {
int i;
myvec v(&i);  // OK in C++17, however, we have partial specialized myvec here...
}


---
User-defined deduction guides:
ref: http://en.cppreference.com/w/cpp/language/class_template_deduction

-
explicit(optional) template-name ( parameter-declaration-clause ) -> simple-template-id ;
-
Example:
https://clang.godbolt.org/z/8hPKWcaT6
template<typename T>
struct Base {
 int data_base = 10;
};

struct Derived : Base {
 int data_derived = 12;
};

template<typename T>
struct myvec {
myvec(T t) {
    cout << t.data_base; // print 10;
}
myvec(float d) {
    cout << "float\n";
}
};

myvec(Derived) -> myvec;

auto main() -> int {
    myvec v(Derived{});
}
-
// declaration of the template
template<class T> struct container {
    container(T t) {}
    template<class Iter> container(Iter beg, Iter end);
};
// additional deduction guide
template<class Iter>
container(Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;
// uses
container c(7); // OK: deduces T=int using an automatic guide
std::vector<double> v = { /* ... */};
auto d = container(v.begin(), v.end()); // OK: deduces T=double
container e{5, 6}; // Error: there is no std::iterator_traits<int>::value_type
--

Explicit deduction guides for class template constructors

Ref:
Class template deduction(since C++17)