Reference:
Template Normal Programming, Part 1 - Arthur O'Dwyer - CppCon 2016.pdfTemplate 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_templatevariable 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)