Error function template partial specialization is not allowed

  • Forum
  • Beginners
  • partial template specialisation

partial template specialisation

Hi there,

I am trying to partially specialise a template with a fixed param, while keeping the other variables. Right now I am hitting a compiler error when trying to partially specialise the template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class A{ };
class B{ };

template <typename T1, typename T2>
void foo1(T1 t1, T2 t2){
   cout << "Not specialised" << endl;
}

template <typename T1>
void foo1<B>(T1 t1, B t2){ // ERROR: template-id ‘foo1<B>’ in declaration of primary template
   cout << "Specialised for the 2nd argument" << endl;
}


template <>
void foo1<B,B>(B t1, B t2){
   cout << "Totally specialised" << endl;
}

int main(int argc, char* argv[]) {
   A a;
   B b;
   foo1<A,A>(a, a); // Not specialised
   foo1<A,B>(a, b); // Specialised for the 2nd argument
   foo1<B,B>(b, b); // Totally specialised

return 0;
}

What is the correct syntax in this case?

Kind regards,
Dean

clang gives a more explicit diagnostic:

test.cc:13:6: error: function template partial specialization is not allowed
void foo1<B>(T1 t1, B t2){ // ERROR: template-id foo1<B> in declaration...
     ^   ~~~

function templates can be either fully specialized or overloaded, not partially-specialized.

shouldn’t line 10 be <B,T1>?

*edit
or <T1,B> rather since t1 is your first template. after thinking about it I dont think you can:

error: function template partial specialization ‘test<b, T1>’ is not allowed
 void test<b,T1>( T1 t1 , b t2 )

**edit looks like cubbi beat me

Last edited on

Function templates can’t be partially specialized; but they overload, and we can use overloading instead.

Function templates can be fully specialized; but even here, we should prefer overloading over specialization.

See http://www.cplusplus.com/forum/beginner/100227/

To give an example of how you could achieve what I think was the goal («a fixed param, while keeping the other variables.»),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <typename T1, typename T2>
void foo1(T1 t1, T2 t2) {
   std::cout << "Not specialisedn";
}

template <typename T1>
void foo1(T1 t1, B t2) {
   std::cout << "first parameter varies, second parameter fixedn";
}

void foo1(B t1, B t2) {
   std::cout << "both parameters fixedn";
}

int main() {
   A a;
   B b;
   foo1(a, a);
   foo1(a, b);
   foo1(b, b);
}

ok, thanks everybody for the replies.

Kind regards,
Dean

Topic archived. No new replies allowed.

Это продолжение Что является эквивалентом параметра функции constexpr? В первоначальном вопросе мы пытаемся ускорить некоторый код, который выполняет сдвиги и вращения под Clang и VC ++. Clang и VC ++ плохо оптимизируют код, потому что он обрабатывает величину сдвига / поворота как переменную (т.е. не constexpr).

Когда я пытаюсь параметризовать величину сдвига и размер слова, это приводит к:

$ g++ -std=c++11 -march=native test.cxx -o test.exe
test.cxx:13:10: error: function template partial specialization is not allowed
uint32_t LeftRotate<uint32_t, unsigned int>(uint32_t v)
^         ~~~~~~~~~~~~~~~~~~~~~~~~
test.cxx:21:10: error: function template partial specialization is not allowed
uint64_t LeftRotate<uint64_t, unsigned int>(uint64_t v)
^         ~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.

Вот тестовая программа. Это немного больше, чем нужно, поэтому люди могут видеть, что нам нужно справиться с обоими uint32_t а также uint64_t (не говоря уже о uint8_t, uint16_t и другие виды).

$ cat test.cxx
#include <iostream>
#include <stdint.h>

template<typename T, unsigned int R>
inline T LeftRotate(unsigned int v)
{
static const unsigned int THIS_SIZE = sizeof(T)*8;
static const unsigned int MASK = THIS_SIZE-1;
return T((v<<R)|(v>>(-R&MASK)));
};

template<uint32_t, unsigned int R>
uint32_t LeftRotate<uint32_t, unsigned int>(uint32_t v)
{
__asm__ ("roll %1, %0" : "+mq" (v) : "I" ((unsigned char)R));
return v;
}

#if __x86_64__
template<uint64_t, unsigned int R>
uint64_t LeftRotate<uint64_t, unsigned int>(uint64_t v)
{
__asm__ ("rolq %1, %0" : "+mq" (v) : "J" ((unsigned char)R));
return v;
}
#endif

int main(int argc, char* argv[])
{
std::cout << "Rotated: " << LeftRotate<uint32_t, 2>((uint32_t)argc) << std::endl;
return 0;
}

Я прошел через несколько итераций сообщений об ошибках в зависимости от того, как я пытаюсь реализовать вращение. Другие сообщения об ошибках включают no function template matches function template specialization.... С помощью template <> кажется, производит самый непонятный.

Как я могу параметризовать величину сдвига в надежде, что Clang и VC ++ оптимизируют вызов функции, как и ожидалось?

3

Решение

Другой способ — превратить шаблонную константу в постоянный аргумент, который компилятор может оптимизировать.

Шаг 1: определить концепцию rotate_distance:

template<unsigned int R> using rotate_distance = std::integral_constant<unsigned int, R>;

Шаг 2: определить функции поворота в терминах перегрузок функции, которая принимает аргумент этого типа:

template<unsigned int R>
uint32_t LeftRotate(uint32_t v, rotate_distance<R>)

Теперь, если мы хотим, мы можем просто позвонить LeftRotate(x, rotate_distance<y>())который, кажется, выражает намерение хорошо,

или теперь мы можем переопределить форму шаблона с двумя аргументами в терминах этой формы:

template<unsigned int Dist, class T>
T LeftRotate(T t)
{
return LeftRotate(t, rotate_distance<Dist>());
}

Полная демонстрация:

#include <iostream>
#include <stdint.h>
#include <utility>

template<unsigned int R> using rotate_distance = std::integral_constant<unsigned int, R>;

template<typename T, unsigned int R>
inline T LeftRotate(unsigned int v, rotate_distance<R>)
{
static const unsigned int THIS_SIZE = sizeof(T)*8;
static const unsigned int MASK = THIS_SIZE-1;
return T((v<<R)|(v>>(-R&MASK)));
}

template<unsigned int R>
uint32_t LeftRotate(uint32_t v, rotate_distance<R>)
{
__asm__ ("roll %1, %0" : "+mq" (v) : "I" ((unsigned char)R));
return v;
}

#if __x86_64__
template<unsigned int R>
uint64_t LeftRotate(uint64_t v, rotate_distance<R>)
{
__asm__ ("rolq %1, %0" : "+mq" (v) : "J" ((unsigned char)R));
return v;
}
#endiftemplate<unsigned int Dist, class T>
T LeftRotate(T t)
{
return LeftRotate(t, rotate_distance<Dist>());
}

int main(int argc, char* argv[])
{
std::cout << "Rotated: " << LeftRotate((uint32_t)argc, rotate_distance<2>()) << std::endl;
std::cout << "Rotated: " << LeftRotate((uint64_t)argc, rotate_distance<2>()) << std::endl;
std::cout << "Rotated: " << LeftRotate<2>((uint64_t)argc) << std::endl;
return 0;
}

до-++ 11 компиляторы

До c ++ 11 у нас не было std :: integra_constant, поэтому мы должны сделать свою собственную версию.

Для наших целей этого достаточно:

template<unsigned int R> struct rotate_distance {};

полное доказательство — обратите внимание на эффект оптимизации:

https://godbolt.org/g/p4tsQ5

2

Другие решения

Используйте шаблонный класс, а не шаблонную функцию:

#include <iostream>
#include <stdint.h>template<typename T, unsigned int R>
struct LeftRotate {
static inline T compute(T v)
{
static const unsigned int THIS_SIZE = sizeof(T)*8;
static const unsigned int MASK = THIS_SIZE-1;
return T((v<<R)|(v>>(-R&MASK)));
}
};template<unsigned int R>
struct LeftRotate<uint32_t, R> {
static inline uint32_t compute(uint32_t v)
{
__asm__ ("roll %1, %0" : "+mq" (v) : "I" ((unsigned char)R));
return v;
}
};

#if __x86_64__
template<unsigned int R>
struct LeftRotate<uint64_t, R> {
static inline uint64_t compute(uint64_t v)
{
__asm__ ("rolq %1, %0" : "+mq" (v) : "J" ((unsigned char)R));
return v;
}
};
#endif

int main(int argc, char* argv[])
{
std::cout << "Rotated: " << LeftRotate<uint32_t, 2>::compute((uint32_t)argc) << std::endl;
return 0;
}

1

What is specialization ?

If you really want to understand templates, you should take a look at functional languages. The world of templates in C++ is a purely functional sublanguage of its own.

In functional languages, selections are done using Pattern Matching:

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a

-- declare function isJust, which takes a Maybe
-- and checks whether it's None or Just
isJust :: Maybe a -> Bool

-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True

As you can see, we overload the definition of isJust.

Well, C++ class templates work exactly the same way. You provide a main declaration, that states the number and nature of the parameters. It can be just a declaration, or also acts as a definition (your choice), and then you can (if you so wish) provide specializations of the pattern and associate to them a different (otherwise it would be silly) version of the class.

For template functions, specialization is somewhat more awkward: it conflicts somewhat with overload resolution. As such, it has been decided that a specialization would relate to a non-specialized version, and specializations would not be considered during overload resolution. Therefore, the algorithm for selecting the right function becomes:

  1. Perform overload resolution, among regular functions and non-specialized templates
  2. If a non-specialized template is selected, check if a specialization exist for it that would be a better match

(for on in-depth treatment, see GotW #49)

As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead.

Is this a template specialization ?

No, it is simply an overload, and this is fine. In fact, overloads usually work as we expect them to, while specializations can be surprising (remember the GotW article I linked).

Since partial specialization is not allowed — as other answers pointed —, you could work around it using std::is_same and std::enable_if, as below:

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
}

Output:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

Edit: In case you need to be able to treat all the other cases left, you could add a definition which states that already treated cases should not match — otherwise you’d fall into ambiguous definitions. The definition could be:

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("either");
}

Which produces:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

Although this all-cases thing looks a bit boring, since you have to tell the compiler everything you’ve already done, it’s quite doable to treat up to 5 or a few more specializations.

Non-class, non-variable partial specialization is not allowed, but as said:

All problems in computer
science can be solved by
another level of indirection. —— David Wheeler

Adding a class to forward the function call can solve this, here is an example:

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");

Function partial specialization is not yet allowed as per the standard. In the example, you are actually overloading & not specializing the max<T1,T2> function.
Its syntax should have looked somewhat like below, had it been allowed:

// Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{                  ^^^^^ <--- [supposed] specializing here
  return 10;
}

In the case of a function templates, only full specialization is allowed by the C++ standard, — excluding the compiler extensions!

Reading Time: 18 minutes

I know, it’s been a while since the last time I published something newbies-friendly on my blog. The main reason is that most of my readers are either experienced devs or from C background having modest C++ encounter. But while programming in C++ you need a completely different mindset as both C & C++ belongs to different programming paradigm. And I always strive to show them a better way of doing things in C++. Anyway, I found the topic which is lengthy, reasonably complex(at least it was for me), newbies-friendly as well as energizing for experienced folks(if Modern C++ jargons, rules & features added) i.e. C++ Template.

I will start with a simple class/function template and as we move along, will increase the complexity. And also cover the advance topics like the variadic template, nested template, CRTP, template vs fold-expression, etc. But, yes! we would not take deeper dive otherwise this would become a book rather than an article.

Note: I would recommend you to use cppinsights online tool wherever you feel confused. It helps you to see Template Instances, Template Argument Deduction, etc. Basically, it helps you to see code from the compiler’s perspective.

Contents

  • 1 Terminology/Jargon/Idiom You May Face
  • 2 C++ Template Types
    • 2.1 Class Template
    • 2.2 Function Template
    • 2.3 Union Template
    • 2.4 Variable Template
  • 3 C++ Template Argument
    • 3.1 Overriding Template Argument Deduction
    • 3.2 Default Template Arguments
    • 3.3 Template Argument Deduction
      • 3.3.1 Function Template Argument Deduction
      • 3.3.2 Class Template Argument Deduction(CTAD)
        • 3.3.2.1 Inferring Template Argument Through Function Template
    • 3.4 Template Argument Forwarding
      • 3.4.1 C++ Template Reference Collapsing Rules
      • 3.4.2 Perfect Forwarding | Forwarding Reference | Universal Reference
      • 3.4.3 Why Do We Need Forwarding Reference in First Place?
  • 4 C++ Template Category
    • 4.1 Full Template Specialization
      • 4.1.1 Function Template Specialization
      • 4.1.2 Class Template Specialization
    • 4.2 Partial Template Specialization
      • 4.2.1 Partial Class Template Specialization
      • 4.2.2 Partial Function Template Specialization
        • 4.2.2.1 Alternative To Partial Function Template Specialization
    • 4.3 Non-Type Template Parameter
    • 4.4 Nested Template: Template Template Parameter
    • 4.5 Variadic Template
      • 4.5.1 Variadic Class Template
        • 4.5.1.1 Implementing Unsophisticated Tuple Class(>=C++14)
        • 4.5.1.2 How Does Variadic Class Template Works?
      • 4.5.2 Variadic Function Template
        • 4.5.2.1 How Does Variadic Function Template Works?
      • 4.5.3 Fold Expressions vs Variadic Template
  • 5 Misc
    • 5.1 C++ Template `typename` vs `class`
      • 5.1.1 To Refer Dependent Types
      • 5.1.2 To Specify Template Template Type
    • 5.2 C++11: Template Type Alias
    • 5.3 C++14/17: Template & auto Keyword
    • 5.4 C++20: Template Lambda Expression
    • 5.5 Explicit Template Instantiation
  • 6 C++ Template Example Use Cases
    • 6.1 Curiously Recurring Template Pattern
    • 6.2 Passing `std` Container as C++ Template Argument
    • 6.3 Passing std::vector to C++ Template Function
      • 6.3.1 Naive Way to Capture Container’s Value Type
      • 6.3.2 Capturing Container’s Value Type Explicitly
    • 6.4 Passing Any Container to C++ Template Function
    • 6.5 Passing Container-of-Container/2D-std::vector as C++ Template Argument
      • 6.5.1 Explicit & Complex Solution
      • 6.5.2 Neat Solution
      • 6.5.3 Generic Solution: Using Variadic Template
    • 6.6 Passing Function to Class Template Argument
  • 7 Conclusion

Terminology/Jargon/Idiom You May Face

  • Template Instantiation: It is a process of generating a concrete class/struct/union/function out of templated class/struct/union/function for a particular combination of template arguments. For example, if you use vector<int> & vector<char>, it will create two different concrete classes during compilation. This process of creating concrete classes is known as Template Instantiation.
  • Template Instances: Outcome of Template Instantiation is Template Instances i.e. concrete classes.
  • Explicit Template Instantiation: Usually template instantiation done at the time of object declaration. But you can also force the compiler to instantiate class/struct/union/function with particular type without even creating the object. It may appear in the program anywhere after the template definition, and for a given argument-list. Will see this later in the article.
  • Template Argument vs Template Parameter: In expression template<typename T> void print(T a){ };, T is parameter & when you call print(5);, 5 which is of type int is template argument. This is a trivial thing for some pips. But not for non-native English speaker or beginners. So, this ambiguity has to be clear.

C++ Template Types

Class Template

template <typename T1, typename T2>
class pair {
public:
    T1  first;
    T2  second;
};

pair<int, char> p1;
pair<float, float> p2;
  • The basic idea of a class template is that the template parameter i.e. T1 & T2 gets substituted by an appropriate deduced type at compile time. The result is that the same class can be reused for multiple types.
  • And the user has to specify which type they want to use when an object of the class is declared.

Function Template

template <typename T>
T min(T a, T b) {
    return a < b ? a : b;
}

min<int>(4, 5);              // Case 1 
min<float>(4.1f, 5.1f);      // Case 2
  • In both of the above case, the template arguments used to replace the types of the parameters i.e. T.
  • One additional property of template functions (unlike class template till C++17) is that the compiler can infer the template parameters based on the parameters passed to the function. So, passing <int> & <float> after the function name is redundant.

Union Template

  • Yes! a union can also be templatized. In fact, the standard library provides some utilities like std::optional, std::variant, etc. which directly or indirectly uses templatized union.
template <typename T>
union test {
    uint8_t     ch[sizeof(T)];
    T           variable;
};
  • As you can see above, templatized unions are also particularly useful to represent a type simultaneously as a byte array.

Variable Template

  • Yes! This may a bit socking. But, you can templatise the variable also since C++14.
template <class T>
constexpr T pi = T(3.1415926535897932385L); // variable template

cout << pi<float> << endl; // 3.14159
cout << pi<int> << endl;   // 3
  • Now, you might be wondering that what is the point of the templatizing variable. But, consider the following example:
template <uint32_t val>
constexpr auto fib = fib<val - 1> + fib<val - 2>;

template <>
constexpr auto fib<0> = 0;

template <>
constexpr auto fib<1> = 1;

cout << fib<10> << endl;    // 55
  • Above code gives you 10th Fibonacci term at compile time, without even creating class or function.

C++ Template Argument

Overriding Template Argument Deduction

template <typename T>
T min(T a, T b) {
    cout << typeid(T).name() << endl; // T will be deduce as `int`
    return a < b ? a : b;
}

min<int>(5.5f, 6.6f);     // Implicit conversion happens here

Default Template Arguments

template <class T, size_t N = 10>
struct array {
    T arr[N];
};

array<int> arr;
  • Just like in case of the function arguments, template parameters can also have their default values.
  • All template parameters with a default value have to be declared at the end of the template parameter list.

Template Argument Deduction

Function Template Argument Deduction

  • Function template argument deduction is done by comparing the types of function arguments to function parameters, according to rules in the Standard. Which makes function templates far more usable than they would otherwise be. For example, given a function template like:
template <typename RanIt> 
void sort(RanIt first, RanIt last){
    // . . .
}
  • You can and should sort a std::vector<int> without explicitly specifying that RanIt is std::vector<int>::iterator. When the compiler sees sort(v.begin(), v.end());, it knows what the types of v.begin() and v.end() are, so it can determine what RanIt should be.

Class Template Argument Deduction(CTAD)

  • Until C++17, template classes could not apply type deduction in their initialization as template function do. For example
//...
pair p4{1, 'A'};               // Not OK until C++17: Can't deduce type in initialization 
//...
  • But from C++17, the compiler can deduce types in class/struct initialization & this to work, class/struct must have an appropriate constructor. But this limitation is also relaxed in C++20. So technically from C++20, you can construct the object with aggregate initialization & without specifying types explicitly.
  • Until C++17, the standard provided some std::make_ utility functions to counter such situations as below.
Inferring Template Argument Through Function Template
  • You might have seen many functions like std::make_pair(), std::make_unique(), std::make_share(), etc. Which can typically & unsophistically implement as:
template <typename T1, typename T2>
pair<T1, T2> make_pair(T1&& t1, T2&& t2) {
    return {forward<T1>(t1), forward<T2>(t2)};
}
  • But have you ever wonder why these helper functions are there in the standard library? How does this even help?
pair<int, char> p1{1, 'A'};          // Rather using this

auto p2 = make_pair(1, 2);           // Use this instead
auto p3 = make_pair<float>(1, 2.4f); // Or specify types explicitly
  • Rather than specifying the arguments explicitly, you can leverage the feature of inferring template argument from function template to construct the object. In the above case, template argument deduction is done by the utility function make_pair. As a result, we have created the object of pair without specifying the type explicitly.
  • And as discussed earlier from C++17, you can construct the object without even specifying types explicitly so std::vector v{1,2,3,4}; is perfectly valid statement.

Template Argument Forwarding

C++ Template Reference Collapsing Rules

  • Apart from accepting type & value in the template parameter. You can enable the template to accept both lvalue and rvalue references. And to do this you need to adhere to the rules of reference collapsing as follows:
    1. T& & becomes T&
    2. T& && become T&
    3. T&& & becomes T&
    4. T&& && becomes T&&
template <typename T>
void f(T &&t);
  • In the above case, the real type of t depends on the context. For example:
int x = 0;

f(0); // deduces as rvalue reference i.e. f(int&&)
f(x); // deduces as lvalue reference i.e. f(int&)
  • In case of f(0);, 0 is rvalue of type int, hence T = int&&, thus f(int&& &&t) becomes f(int&& t).
  • In case of f(x);, x is lvalue of type int, hence T = int&, thus f(int& &&t) becomes f(int& t).

Perfect Forwarding | Forwarding Reference | Universal Reference

  • In order to perfectly forward t to another function, one must use std::forward as:
template <typename T>
void func1(T &&t) {
    func2(std::forward<T>(t));  // Forward appropriate lvalue or rvalue reference to another function
}
  • Forwarding references can also be used with variadic templates:
template <typename... Args>
void func1(Args&&... args) {
    func2(std::forward<Args>(args)...);
}

Why Do We Need Forwarding Reference in First Place?

  • Answer to this question lies in move semantics. Though, short answer to this question is “To perform copy/move depending upon value category type”.

C++ Template Category

Full Template Specialization

  • Template has a facility to define implementation for specific instantiations of a template class/struct/union/function/method.

Function Template Specialization

template <typename T>
T sqrt(T t) { /* Some generic implementation */ }

template<>
int sqrt<int>(int i) { /* Highly optimized integer implementation */ }
  • In the above case, a user that writes sqrt(4.0) will get the generic implementation whereas sqrt(4) will get the specialized implementation.

Class Template Specialization

template <typename T>       // Common case
struct Vector {
    void print() {}
};

template <>                 // Special case
struct Vector<bool> {
    void print_bool() {}
};

Vector<int> v1;
v1.print_bool();    // Not OK: Chose common case Vector<T>
v1.print()          // OK

Vector<bool> v2;    // OK : Chose special case Vector<bool>

Partial Template Specialization

Partial Class Template Specialization

  • In contrast of a full template specialization, you can also specialise template partially with some of the arguments of existing template fixed. Partial template specialization is only available for template class/structs/union:
template <typename T1, typename T2>     // Common case
struct Pair {
    T1 first;
    T2 second;

    void print_first() {}
};

template <typename T>    // Partial specialization on first argument as int
struct Pair<int, T> {
    void print() {}
};

// Use case 1 ----------------------------------------------------------
Pair<char, float> p1;    // Chose common case
p1.print_first();        // OK
// p1.print();           // Not OK: p1 is common case & it doesn't have print() method

// Use case 2 ----------------------------------------------------------
Pair<int, float> p2;     // Chose special case
p2.print();              // OK
// p2.print_first();     // Not OK: p2 is special case & it does not have print_first()

// Use case 3 ----------------------------------------------------------
// Pair<int> p3;         // Not OK: Number of argument should be same as Primary template

Partial Function Template Specialization

  • You cannot partially specialize method/function. Function templates may only be fully specialized
template <typename T, typename U>
void foo(T t, U u) {
    cout << "Common case" << endl;
}

// OK.
template <>
void foo<int, int>(int a1, int a2) {
    cout << "Fully specialized case" << endl;
}

// Compilation error: partial function specialization is not allowed.
template <typename U>
void foo<string, U>(string t, U u) {
    cout << "Partial specialized case" << endl;
}

foo(1, 2.1); // Common case
foo(1, 2);   // Fully specialized case
Alternative To Partial Function Template Specialization
  • As I have mentioned earlier, partial specialization of function templates is not allowed. You can use SFINAE with std::enable_if for work around as follows:
template <typename T, typename std::enable_if_t<!std::is_pointer<T>::value> * = nullptr>
void func(T val) {  
    cout << "Value" << endl; 
}

template <typename T, typename std::enable_if_t<std::is_pointer<T>::value> * = nullptr>
void func(T val) {  // NOTE: function signature is NOT-MODIFIED
    cout << "Pointer" << endl; 
}

int a = 0;
func(a);
func(&a);

Non-Type Template Parameter

  • As the name suggests, apart from types, you can also declare the template parameter as constant expressions like addresses, references, integrals, std::nullptr_t, enums, etc.
  • Like all other template parameters, non-type template parameters can be explicitly specified, defaulted, or derived implicitly via Template Argument Deduction.
  • The more specific use case of a non-type template is passing a plain array into a function without specifying its size explicitly. A more relevant example of this is std::begin & std::end specialisation for array literal from the standard library:
template <  class T, 
            size_t size>     // Non Type Template
T* begin(T (&arr)[size]) {   // Array size deduced implicitly
    return arr;
}

int arr[] = {1,2,3,4};
begin(arr);                  // Do not have to pass size explicitly 
  • Non-type template parameters are one of the ways to achieve template recurrence & enables Template Meta-programming.

Nested Template: Template Template Parameter

  • Sometimes we have to pass templated type into another templated type. And in such case, you not only have to take care of main template type but also a nested template type. Very simple template- template parameter examples is:
template<   
            template <typename> class C, 
            typename T
        >
void print_container(C<T> &c) {
    // . . .
}

template <typename T>
class My_Type {
    // . . .
};

My_Type<int> t;
print_container(t);

Variadic Template

  • It is often useful to define class/struct/union/function that accepts a variable number and type of arguments.
  • If you have already used C you’ll know that printf function can accept any number of arguments. Such functions are entirely implemented through macros or ellipses operator. And because of that it has several disadvantages like type-safety, cannot accept references as arguments, etc.

Variadic Class Template

Implementing Unsophisticated Tuple Class(>=C++14)
  • Since C++11 standard library introduced std::tuple class that accept variable data members at compile time using the variadic template. And to understand its working, we will build our own ADT same as std::tuple
  • The variadic template usually starts with the general (empty) definition, that also serves as the base-case for recursion termination in the later specialisation:
template <typename... T>
struct Tuple { };
  • This already allows us to define an empty structure i.e. Tuple<> object;, albeit that isn’t very useful yet. Next comes the recursive case specialisation:
template<
            typename T, 
            typename... Rest
        >
struct Tuple<T, Rest...> {
    T               first;
    Tuple<Rest...>  rest;

    Tuple(const T& f, const Rest& ... r)
        : first(f)
        , rest(r...) {
    }
};

Tuple<bool> t1(false);                      // Case 1
Tuple<int, char, string> t2(1, 'a', "ABC"); // Case 2
How Does Variadic Class Template Works?

To understand variadic class template, consider use case 2 above i.e. Tuple<int, char, string> t2(1, 'a', "ABC");

  • The declaration first matches against the specialization, yielding a structure with int first; and Tuple<char, string> rest; data members.
  • The rest definition again matches with specialization, yielding a structure with char first; and Tuple<string> rest; data members.
  • The rest definition again matches this specialization, creating its own string first; and Tuple<> rest; members.
  • Finally, this last rest matches against the base-case definition, producing an empty structure.

You can visualize this as follows:

Tuple<int, char, string>
-> int first
-> Tuple<char, string> rest
    -> char first
    -> Tuple<string> rest
        -> string first
        -> Tuple<> rest
            -> (empty)

I have written a separate article on Variadic Template C++: Implementing Unsophisticated Tuple, if you are interested more in the variadic temple.

Variadic Function Template

  • As we have seen earlier, variadic template starts with empty definition i.e. base case for recursion.
void print() {}
  • Then the recursive case specialisation:
template<   
            typename First, 
            typename... Rest                    // Template parameter pack
        >     
void print(First first, Rest... rest) {         // Function parameter pack
    cout << first << endl;
    print(rest...);                             // Parameter pack expansion
} 
  • This is now sufficient for us to use the print function with variable number and type of arguments. For example:
print(500, 'a', "ABC");
  • You can further optimize the print function with forwarding reference, if constexpr() & sizeof() operator as:
template<   
            typename First, 
            typename... Rest
        >     
void print(First&& first, Rest&&... rest) {         
    if constexpr(sizeof...(rest) > 0) {             // Size of parameter pack
        cout << first << endl;
        print(std::forward<Rest>(rest)...);         // Forwarding reference
    }
    else {
        cout << first << endl;
    }
} 
How Does Variadic Function Template Works?
  • As you can see we have called print with 3 arguments i.e. print(500, 'a', "ABC");
  • At the time of compilation compiler instantiate 3 different print function as follows:
    1. void print(int first, char __rest1, const char* __rest2)
    2. void print(char first, const char* __rest1)
    3. void print(const char* first)
  • The first print(i.e. accept 3 arguments) will be called which prints the first argument & line print(rest…); expand with second print(i.e. accept 2 arguments). This will go on till argument count reaches to zero.
  • That means in each call to print, the number of arguments is reduced by one & the rest of the arguments will be handled by a subsequent instance of print.
  • Thus, the number of print instance after compilation is equal to the number of arguments, plus the base case instance of print. Hence, the variadic template also contributes to more code bloating.
  • You can get this much better if you put the above example in cppinsights. And try to understand all the template instances.

Fold Expressions vs Variadic Template

  • As we saw, from C++11, the variadic template is a great addition to C++ Template. But it has nuisance like you need base case & recursive template implementation, etc.
  • So, with C++17 standard introduced a new feature named as Fold Expression. Which you can use with parameter pack as follows:
template <typename... Args>
void print(Args &&... args) {
    (void(cout << std::forward<Args>(args) << endl), ...);
}
  • See, no cryptic boilerplate required. Isn’t this solution looks neater?
  • There are total 3 types of folding: Unary fold, Binary fold & Fold over a comma. Here we have done left folding over a comma. You can read more about Fold Expression here.

Misc

C++ Template `typename` vs `class`

  • typename and class are interchangeable in most of the cases.
  • A general convention is typename used with the concrete type(i.e. in turn, does not depend on further template parameter) while class used with dependent type.
  • But there are cases where either typename or class has to be certain. For example

To Refer Dependent Types

template<typename container>
class Example {
    using t1 = typename container::value_type; // value_type depends on template argument of container
    using t2 = std::vector<int>::value_type;   // value_type is concrete type, so doesn't require typename
};
  • typename is a must while referencing a nested type that depends on template parameter.

To Specify Template Template Type

template<
            template <typename, typename> class C, // `class` is must prior to C++17
            typename T, 
            typename Allocator
        >
void print_container(C<T, Allocator> container) {
    for (const T& v : container)
        cout << v << endl;
}

vector<int> v;
print_container(v);
  • This is rectified in C++17, So now you can use typename also.

C++11: Template Type Alias

template<typename T> 
using pointer = T*;

pointer<int> p = new int;   // Equivalent to: int* p = new int;


template <typename T>
using v = vector<T>;

v<int> dynamic_arr;         // Equivalent to: vector<int> dynamic_arr;
  • typedef will also work fine, but would not encourage you to use. As it isn’t part of Modern C++.

C++14/17: Template & auto Keyword

  • Since C++14, you can use auto in function argument. It’s kind of template shorthand as follows:
void print(auto &c) { /*. . .*/ }

// Equivalent to

template <typename T>
void print(T &c) { /*. . .*/ }
  • Although auto in function return-type is supported from C++11. But, you have to mention the trailing return type. Which is rectified in C++14 & now return type is automatically deduced by compiler.
  • From C++17, you can also use auto in non-type template(I will cover this in later part this article) parameters.

C++20: Template Lambda Expression

  • A generic lambda expression is supported since C++14 which declare parameters as auto. But there was no way to change this template parameter and use real template arguments. For example:
template <typename T>
void f(std::vector<T>&	vec) {
	//. . .
}
  • How do you write the lambda for the above function which takes std::vector of type T? This was the limitation till C++17, but with C++20 it is possible templatized lambda as :
auto f = []<typename T>(std::vector<T>&  vec) {
    // . . .
};

std::vector<int> v;
f(v);

Explicit Template Instantiation

  • An explicit instantiation creates and declares a concrete class/struct/union/function/variable from a template, without using it just yet.
  • Generally, you have to implement the template in header files only. You can not put the implementation/definition of template methods in implementation files(i.e. cpp or .cc). If this seems new to you, then consider following minimalist example:

value.hpp

#pragma once

template <typename T>
class value {
    T val;
public:
    T get_value();
};

value.cpp

#include "value.hpp"

template <typename T>
T value<T>::get_value() { 
    return val; 
}

main.cpp

#include "value.hpp"

int main() {
    value<int> v1{9};
    cout << v1.get_value() << endl;
    return 0;
}
  • If you compile above code you will get following error:
/tmp/main-4b4bef.o: In function `main':
main.cpp:(.text+0x1e): undefined reference to `value<int>::get_value()'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
compiler exit status 1
  • If you do explicit initialization i.e. add template class value<int>; line at the end of value.cpp. Then the compilation gets successful.
  • The “template class” command causes the compiler to explicitly instantiate the template class. In the above case, the compiler will stencil out value<int> inside of value.cpp.
  • There are other solutions as well. Check out this StackOverflow link.

C++ Template Example Use Cases

Curiously Recurring Template Pattern

  • CRTP widely employed for static polymorphism or code reusability without bearing the cost of virtual dispatch mechanism. Consider the following code:
template <typename specific_animal>
struct animal {
    void who() { implementation().who(); }

private:
    specific_animal &implementation() { return *static_cast<specific_animal *>(this); }
};

struct dog : animal<dog> {
    void who() { cout << "dog" << endl; }
};

struct cat : animal<cat> {
    void who() { cout << "cat" << endl; }
};


template <typename specific_animal>
void who_am_i(animal<specific_animal> *animal) {
    animal->who();
}


who_am_i(new dog); // Prints `dog`
who_am_i(new cat); // Prints `cat`
  • We have not used virtual keyword & still achieved the functionality of polymorphism(more-specifically static polymorphism).
  • I have written a separate article covering practical Examples of Curiously Recurring Template Pattern(CRTP).

Passing `std` Container as C++ Template Argument

  • If you wanted to accept anything and figure it out later, you could write:
template <typename C>
void print_container(const C &container) {
    for (const auto &v : container)
        cout << v << endl;
}
  • This naive way may fail if you pass anything other than standard container as other types may not have begin & end iterator.

Passing std::vector to C++ Template Function

Naive Way to Capture Container’s Value Type

  • But let say, you want to pass container & want to work with container’s storage type also. You can do:
template<
            typename C, 
            typename T = typename C::value_type
        >
void print_container(const C &container) {
    for (const T &v : container)
        cout << v << endl;
}
  • We can provide the second type parameter to our function that uses SFINAE to verify that the thing is actually a container.
  • All standard containers have a member type named value_type which is the type of the thing inside the container. We sniff for that type, and if no such type exists, then SFINAE kicks in, and that overload is removed from consideration.

Capturing Container’s Value Type Explicitly

  • But what if you are passing vector class which doesn’t has value_type member?
  • std::vector is defined as:
template<
            class T,
            class Allocator = std::allocator<T>
        > 
class vector;
  • And you can capture two template arguments of std::vector container explicitly as:
template<
            template <typename, typename> class C, 
            typename T, 
            typename Allocator
        >
void print_container(C<T, Allocator> container) {
    for (const T& v : container)
        cout << v << endl;
}
  • Above template pattern would be same if you want pass container to class/struct/union.

Passing Any Container to C++ Template Function

  • You see if you pass any other containers to the above solution. It won’t work. So to make it generic we can use variadic template:
template<
            template <typename...> class C, 
            typename... Args
        >
void print_container(C<Args...> container) {
    for (const auto &v : container)
        cout << v << endl;
}

vector<int>     v{1, 2, 3, 4}; // takes total 2 template type argument
print_container(v); 

set<int>        s{1, 2, 3, 4}; // takes total 3 template type argument
print_container(s);

Passing Container-of-Container/2D-std::vector as C++ Template Argument

  • This is the case of nested template i.e. template-template parameter. And there are the following solutions:

Explicit & Complex Solution

template<
            template <typename, typename> class C1,
            template <typename, typename> class C2,
            typename Alloc_C1, typename Alloc_C2,
            typename T
        >
void print_container(const C1<C2<T, Alloc_C2>, Alloc_C1> &container) {
    for (const C2<T, Alloc_C2> &container_in : container)
        for (const T &v : container_in)
            cout << v << endl;
}
  • I know this is ugly, but seems more explicit.

Neat Solution

template<   
            typename T1,
            typename T2 = typename T1::value_type,
            typename T3 = typename T2::value_type
        >
void print_container(const T1 &container) {
    for (const T2 &e : container)
        for (const T3 &x : e)
            cout << x << endl;
}
  • As seen earlier including SFINAE.

Generic Solution: Using Variadic Template

template<
            template <typename...> class C, 
            typename... Args
        >
void print_container(C<Args...> container) {
    for (const auto &container_2nd : container)
        for (const auto &v : container_2nd)
            cout << v << endl;
}
  • This is our standard solution using the variadic template will work for a single container or any number of the nested container.

Passing Function to Class Template Argument

  • Passing class/struct/union to another class/struct/union as template argument is common thing. But passing function to class/struct/union as template argument is bit rare. But yes it’s possible indeed. Consider the Functional Decorator using a variadic class template.
// Need partial specialization for this to work
template <typename T>
struct Logger;

// Return type and argument list
template <typename R, typename... Args>
struct Logger<R(Args...)> {
    function<R(Args...)>    m_func;
    string                  m_name;
    Logger(function<R(Args...)> f, const string &n) : m_func{f}, m_name{n} { }
 
    R operator()(Args... args) {
        cout << "Entering " << m_name << endl;
        R result = m_func(args...);
        cout << "Exiting " << m_name << endl;
        return result;
    }
};

template <typename R, typename... Args>
auto make_logger(R (*func)(Args...), const string &name) {
    return Logger<R(Args...)>(function<R(Args...)>(func), name);
}

double add(double a, double b) { return a + b; }

int main() {
    auto logged_add = make_logger(add, "Add");
    auto result = logged_add(2, 3);
    return EXIT_SUCCESS;
}
  • Above example may seem a bit complex to you at first sight. But if you have a clear understanding of variadic class temple then it won’t take more than 30 seconds to understand what’s going on here.

Conclusion

I hope I have covered most of the topics around C++ Template. And yes, this was a very long & intense article. But I bet you that if you do master the C++ template well, it will really give you an edge. And also open a door to sub-world of C++ i.e. template meta-programming.

C++ Template: C++’s its own interpreted sub language

– IndianWestCoast

Do you like it☝️? Get such articles directly into the inbox…!?

Следующий код выдает ошибку компиляции: class Q64 is not a valid type for a template constant parameter

template<int GRIDD, class T>
INLINE T grid_residue(T amount) {
  T rem = amount%(GRIDD);
  if (rem > GRIDD/2) rem -= GRIDD;
  return rem;
}


template<int GRIDD, Q64>
INLINE Q64 grid_residue(Q64 amount) {
  return Q64(grid_residue<GRIDD, int64_t>(to_int(amount)));
}

Что не так? Я пытаюсь специализироваться grid_residue для класса Q64.

ОБНОВИТЬ:

Изменен синтаксис. Теперь появляется ошибка error: function template partial specialization 'grid_residue<GRIDD, Q64>' is not allowed

template<int GRIDD>
INLINE Q64 grid_residue(Q64 amount) {
    return Q64(grid_residue<GRIDD, int>(to_int(amount)));
}

благодаря

3 ответы

Функции не могут быть частично специализированы! Либо используйте перегрузку функции: template <int GRIDD> inline Q64 grid_residue(Q64 amount) или оберните вашу функцию в тип (который может быть частично специализированным).

Вы не можете частично специализировать функции.

Создан 05 янв.

struct test
{

};

template<int GRIDD, class T>
T grid_residue(T amount) 
{
    std::cout << "template<int GRIDD, class T> T grid_residue(T amount)" << " GRIDD: " << GRIDD << std::endl;
    return T();
}


template<int GRIDD>
test grid_residue(test amount) 
{
    std::cout << "template<int GRIDD> test grid_residue(test amount)" << " GRIDD: " << GRIDD << std::endl;
    int inp = 0;
    grid_residue<GRIDD,int>(inp);
    return test();
}


int 
_tmain(int argc, _TCHAR* argv[])
{
    test amount;
    grid_residue<19>(amount);
    std::string inValue;
    grid_residue<19>(inValue);
}

Компилирует / линки в порядке (VS2010).

Создан 05 янв.

Вступление

Классы, функции и (поскольку C ++ 14) могут быть шаблонами. Шаблон представляет собой фрагмент кода с некоторыми свободными параметрами, которые станут конкретным классом, функцией или переменной, когда будут указаны все параметры. Параметры могут быть типами, значениями или самими шаблонами. Известным шаблоном является std::vector , который становится конкретным типом контейнера, когда указан тип элемента, например std::vector<int> .

Синтаксис

  • шаблон < шаблон-параметр-список > декларация
  • export template < template-parameter-list > объявление / * до C ++ 11 * /
  • шаблон <> Объявление
  • объявление шаблона
  • объявление шаблона extern / *, поскольку C ++ 11 * /
  • template < template-parameter-list > class … ( opt ) identifier ( opt )
  • template < template-parameter-list > идентификатор класса ( opt ) = id-expression
  • template < template-parameter-list > typename … ( opt ) identifier ( opt ) / *, поскольку C ++ 17 * /
  • template < template-parameter-list > typename identifier ( opt ) = id-expression / *, поскольку C ++ 17 * /
  • postfix-expression . Идентификатор шаблона
  • postfix-expression -> идентификатор шаблона
  • template вложенного имени-спецификатора simple-template-id ::

замечания

template слова — это ключевое слово с пятью различными значениями на языке C ++, в зависимости от контекста.

  1. Когда за ним следует список параметров шаблона, заключенных в <> , он объявляет шаблон, такой как шаблон класса, шаблон функции или частичную специализацию существующего шаблона.

    template <class T>
    void increment(T& x) { ++x; }
    
  2. Когда следует пустой <> , он объявляет явную (полную) специализацию .

    template <class T>
    void print(T x);
    
    template <> // <-- keyword used in this sense here
    void print(const char* s) {
        // output the content of the string
        printf("%sn", s);
    }
    
  3. При последующей декларации без <> , она образует явное инстанцирование декларацию или определение.

    template <class T>
    std::set<T> make_singleton(T x) { return std::set<T>(x); }
    
    template std::set<int> make_singleton(int x); // <-- keyword used in this sense here
    
  4. В списке параметров шаблона он вводит параметр шаблона шаблона .

    template <class T, template <class U> class Alloc>
    //                 ^^^^^^^^ keyword used in this sense here
    class List {
        struct Node {
            T value;
            Node* next;
        };
        Alloc<Node> allocator;
        Node* allocate_node() {
            return allocator.allocate(sizeof(T));
        }
        // ...
    };
    
  5. После оператора разрешения области видимости :: и операторов доступа к классу . и -> , он указывает, что следующее имя является шаблоном.

    struct Allocator {
        template <class T>
        T* allocate();
    };
    
    template <class T, class Alloc>
    class List {
        struct Node {
            T value;
            Node* next;
        }
        Alloc allocator;
        Node* allocate_node() {
            // return allocator.allocate<Node>();       // error: < and > are interpreted as
                                                        // comparison operators
            return allocator.template allocate<Node>(); // ok; allocate is a template
            //               ^^^^^^^^ keyword used in this sense here
        }
    };
    

Перед C ++ 11 шаблон можно было объявить с ключевым словом export , превратив его в экспортированный шаблон. Определение экспортируемого шаблона не обязательно должно присутствовать в каждой единицы перевода, в которой создается экземпляр шаблона. Например, должно было работать следующее:

foo.h :

#ifndef FOO_H
#define FOO_H
export template <class T> T identity(T x);
#endif

foo.cpp :

#include "foo.h"
template <class T> T identity(T x) { return x; }

main.cpp :

#include "foo.h"
int main() {
    const int x = identity(42); // x is 42
}

Из-за сложности выполнения ключевое слово export не поддерживалось большинством основных компиляторов. Он был удален в C ++ 11; В настоящее время запрещено использовать ключевое слово export вообще. Вместо этого обычно необходимо определять шаблоны в заголовках (в отличие от функций без шаблонов, которые обычно не определены в заголовках). См. Почему шаблоны могут быть реализованы только в файле заголовка?

Шаблоны функций

Шаблон может также применяться к функциям (а также к более традиционным структурам) с тем же эффектом.

// 'T' stands for the unknown type
// Both of our arguments will be of the same type.
template<typename T>
void printSum(T add1, T add2)
{
    std::cout << (add1 + add2) << std::endl;
}

Затем это можно использовать так же, как шаблоны структуры.

printSum<int>(4, 5);
printSum<float>(4.5f, 8.9f);

В обоих случаях аргумент шаблона используется для замены типов параметров; результат работает так же, как и обычная C ++-функция (если параметры не соответствуют типу шаблона, то компилятор применяет стандартные преобразования).

Еще одно свойство шаблонных функций (в отличие от классов шаблонов) заключается в том, что компилятор может вывести параметры шаблона на основе параметров, переданных функции.

printSum(4, 5);     // Both parameters are int.
                    // This allows the compiler deduce that the type
                    // T is also int.

printSum(5.0, 4);   // In this case the parameters are two different types.
                    // The compiler is unable to deduce the type of T
                    // because there are contradictions. As a result
                    // this is a compile time error.

Эта функция позволяет нам упростить код, когда мы объединяем структуры и функции шаблонов. В стандартной библиотеке существует общий шаблон, который позволяет нам создавать template structure X с помощью вспомогательной функции make_X() .

// The make_X pattern looks like this.
// 1) A template structure with 1 or more template types.
template<typename T1, typename T2>
struct MyPair
{
    T1      first;
    T2      second;
};
// 2) A make function that has a parameter type for
//    each template parameter in the template structure.
template<typename T1, typename T2>
MyPair<T1, T2> make_MyPair(T1 t1, T2 t2)
{
    return MyPair<T1, T2>{t1, t2};
}

Как это помогает?

auto val1 = MyPair<int, float>{5, 8.7};     // Create object explicitly defining the types
auto val2 = make_MyPair(5, 8.7);            // Create object using the types of the paramters.
                                            // In this code both val1 and val2 are the same
                                            // type.

Примечание. Это не предназначено для сокращения кода. Это сделано для того, чтобы сделать код более надежным. Он позволяет изменять типы, изменяя код в одном месте, а не в нескольких местах.

Переадресация аргументов

Шаблон может принимать как значения lvalue, так и rvalue с использованием ссылки пересылки :

template <typename T>
void f(T &&t);

В этом случае реальный тип t будет выведен в зависимости от контекста:

struct X { };

X x;
f(x); // calls f<X&>(x)
f(X()); // calls f<X>(x)

В первом случае тип T выводится как ссылка на X ( X& ), а тип t является ссылкой на X , а во втором случае тип T выводится как X и тип t качестве ссылки на rvalue к X ( X&& ).

Примечание. Стоит заметить, что в первом случае decltype(t) совпадает с T , но не во втором.

Чтобы отлично перевести t в другую функцию, будь то ссылка lvalue или rvalue, нужно использовать std::forward :

template <typename T>
void f(T &&t) {
    g(std::forward<T>(t));
}

Ссылки на пересылку могут использоваться с вариационными шаблонами:

template <typename... Args>
void f(Args&&... args) {
    g(std::forward<Args>(args)...);
}

Примечание. Ссылки на перенаправление могут использоваться только для параметров шаблона, например, в следующем коде, v является ссылкой на rvalue, а не ссылкой на пересылку:

#include <vector>

template <typename T>
void f(std::vector<T> &&v);

Шаблон базового класса

Основная идея шаблона класса заключается в том, что параметр шаблона заменяется типом во время компиляции. В результате один и тот же класс может быть повторно использован для нескольких типов. Пользователь указывает, какой тип будет использоваться при объявлении переменной класса. Три примера этого показаны в main() :

#include <iostream>
using std::cout;

template <typename T>         // A simple class to hold one number of any type
class Number {
public:
    void setNum(T n);         // Sets the class field to the given number
    T plus1() const;          // returns class field's "follower"
private:
    T num;                    // Class field
};

template <typename T>         // Set the class field to the given number
void Number<T>::setNum(T n) {
    num = n;
}

template <typename T>         // returns class field's "follower"
T Number<T>::plus1() const {
    return num + 1;
}

int main() {
    Number<int> anInt;        // Test with an integer (int replaces T in the class)
    anInt.setNum(1);
    cout << "My integer + 1 is " << anInt.plus1() << "n";     // Prints 2

    Number<double> aDouble;   // Test with a double
    aDouble.setNum(3.1415926535897);
    cout << "My double + 1 is " << aDouble.plus1() << "n";    // Prints 4.14159

    Number<float> aFloat;     // Test with a float
    aFloat.setNum(1.4);
    cout << "My float + 1 is " << aFloat.plus1() << "n";      // Prints 2.4

    return 0;  // Successful completion
}

Шаблонная специализация

Вы можете определить реализацию для конкретных экземпляров класса шаблона / метода.

Например, если у вас есть:

template <typename T>
T sqrt(T t) { /* Some generic implementation */ }

Затем вы можете написать:

template<>
int sqrt<int>(int i) { /* Highly optimized integer implementation */ }

Тогда пользователь, который пишет sqrt(4.0) , получит общую реализацию, тогда как sqrt(4) получит специализированную реализацию.

Специализация частичного шаблона

В отличие от полной специализированной специализированной специализации шаблона шаблона, можно ввести шаблон с некоторыми аргументами существующего шаблона. Специализация частичного шаблона доступна только для шаблона class / structs:

// Common case:
template<typename T, typename U>
struct S {
    T t_val;
    U u_val;
};

// Special case when the first template argument is fixed to int
template<typename V>
struct S<int, V> {
    double another_value;
    int foo(double arg) {// Do something}
};

Как показано выше, частичные специализированные шаблоны могут вводить совершенно разные наборы данных и членов функций.

Когда создается экземпляр частично специализированного шаблона, выбирается наиболее подходящая специализация. Например, давайте определим шаблон и две частичные специализации:

template<typename T, typename U, typename V>
struct S {
    static void foo() {
        std::cout << "General casen";
    }
};

template<typename U, typename V>
struct S<int, U, V> {
    static void foo() {
        std::cout << "T = intn";
    }
};

template<typename V>
struct S<int, double, V> {
    static void foo() {
        std::cout << "T = int, U = doublen";
    }
};

Теперь следующие вызовы:

S<std::string, int, double>::foo();
S<int, float, std::string>::foo();
S<int, double, std::string>::foo();

распечатает

General case
T = int
T = int, U = double

Шаблоны функций могут быть полностью специализированными:

template<typename T, typename U>
void foo(T t, U u) {
    std::cout << "General case: " << t << " " << u << std::endl;
}

// OK.
template<>
void foo<int, int>(int a1, int a2) {
    std::cout << "Two ints: " << a1 << " " << a2 << std::endl;
}

void invoke_foo() {
    foo(1, 2.1); // Prints "General case: 1 2.1"
    foo(1,2);    // Prints "Two ints: 1 2"
}

// Compilation error: partial function specialization is not allowed.
template<typename U>
void foo<std::string, U>(std::string t, U u) {
    std::cout << "General case: " << t << " " << u << std::endl;
}

Значение параметра шаблона по умолчанию

Как и в случае аргументов функции, параметры шаблона могут иметь значения по умолчанию. Все параметры шаблона со значением по умолчанию должны быть объявлены в конце списка параметров шаблона. Основная идея заключается в том, что параметры шаблона со значением по умолчанию могут быть опущены при создании шаблона.

Простой пример использования значения параметра шаблона по умолчанию:

template <class T, size_t N = 10>
struct my_array {
    T arr[N];
};

int main() {
    /* Default parameter is ignored, N = 5 */
    my_array<int, 5> a;

    /* Print the length of a.arr: 5 */
    std::cout << sizeof(a.arr) / sizeof(int) << std::endl;

    /* Last parameter is omitted, N = 10 */
    my_array<int> b;

    /* Print the length of a.arr: 10 */
    std::cout << sizeof(b.arr) / sizeof(int) << std::endl;
}

Шаблон псевдонимов

C ++ 11

Основной пример:

template<typename T> using pointer = T*;

Это определение делает pointer<T> псевдоним T* . Например:

pointer<int> p = new int; // equivalent to: int* p = new int;

Шаблоны псевдонимов не могут быть специализированными. Однако эту функцию можно получить косвенно, если они ссылаются на вложенный тип в структуре:

template<typename T>
 struct nonconst_pointer_helper { typedef T* type; };

template<typename T>
 struct nonconst_pointer_helper<T const> { typedef T* type; };

template<typename T> using nonconst_pointer = nonconst_pointer_helper<T>::type;

Параметры шаблона шаблона

Иногда мы хотели бы перейти в шаблон типа шаблона без фиксации его значений. Для этого создаются параметры шаблона шаблона. Очень простые примеры шаблонов шаблонов:

template <class T>
struct Tag1 { };

template <class T>
struct Tag2 { };

template <template <class> class Tag>
struct IntTag {
   typedef Tag<int> type;
};

int main() {
   IntTag<Tag1>::type t;
}

C ++ 11

#include <vector>
#include <iostream>

template <class T, template <class...> class C, class U>
C<T> cast_all(const C<U> &c) {
   C<T> result(c.begin(), c.end());
   return result;
}

int main() {
   std::vector<float> vf = {1.2, 2.6, 3.7};
   auto vi = cast_all<int>(vf);
   for(auto &&i: vi) {
      std::cout << i << std::endl;
   }
}

Объявление аргументов типа non-type с авто

До C ++ 17, когда вы пишете параметр непигового шаблона, вам нужно сначала указать его тип. Таким образом, общая картина стала писать что-то вроде:

template <class T, T N>
struct integral_constant {
    using type = T;
    static constexpr T value = N;
};

using five = integral_constant<int, 5>;

Но для сложных выражений, используя что-то вроде этого, приходится писать decltype(expr), expr при создании шаблонов. Решение состоит в том, чтобы упростить эту идиому и просто разрешить auto :

C ++ 17

template <auto N>
struct integral_constant {
    using type = decltype(N); 
    static constexpr type value = N;
};

using five = integral_constant<5>;

Пустой пользовательский отладчик для unique_ptr

Хороший пример мотивации может прийти от попыток объединить пустую оптимизацию базы с пользовательской Deleter для unique_ptr . Различные C API-дескрипторы имеют разные типы возврата, но нам все равно — мы просто хотим, чтобы что-то работало для любой функции:

template <auto DeleteFn>
struct FunctionDeleter {
    template <class T>
    void operator()(T* ptr) const {
        DeleteFn(ptr);
    }
};

template <T, auto DeleteFn>
using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;

И теперь вы можете просто использовать любой указатель на функцию, который может принимать аргумент типа T в качестве параметра шаблона без типа, независимо от типа возвращаемого значения, и получать из него нестандартные служебные данные unique_ptr :

unique_ptr_deleter<std::FILE, std::fclose> p;

Параметр шаблона непигового типа

Помимо типов в качестве параметра шаблона нам разрешено объявлять значения константных выражений, удовлетворяющие одному из следующих критериев:

  • интегрального или перечисляемого типа,
  • указатель на объект или указатель на функцию,
  • lvalue ссылка на объект или lvalue ссылку на функцию,
  • указатель на член,
  • std::nullptr_t .

Как и все параметры шаблона, параметры шаблона непигового типа могут быть явно заданы, дефолтны или производятся неявно через вычитание аргумента шаблона.

Пример использования параметров шаблона непигового типа:

#include <iostream>

template<typename T, std::size_t size>
std::size_t size_of(T (&anArray)[size])  // Pass array by reference. Requires.
{                                        // an exact size. We allow all sizes
    return size;                         // by using a template "size".
}

int main()
{
    char anArrayOfChar[15];
    std::cout << "anArrayOfChar: " << size_of(anArrayOfChar) << "n";

    int  anArrayOfData[] = {1,2,3,4,5,6,7,8,9};
    std::cout << "anArrayOfData: " << size_of(anArrayOfData) << "n";
}

Пример явного указания параметров шаблона типа и не-типа:

#include <array>
int main ()
{
    std::array<int, 5> foo; // int is a type parameter, 5 is non-type
}

Нестандартные параметры шаблона являются одним из способов достижения повторения шаблона и позволяют выполнять метапрограммирование .

Структуры данных шаблонов Variadic

C ++ 14

Часто бывает полезно определить классы или структуры, которые имеют переменное число и тип элементов данных, которые определены во время компиляции. Канонический пример — std::tuple , но иногда необходимо определить собственные пользовательские структуры. Вот пример, который определяет структуру с использованием рецептуры (а не наследования, как и для std::tuple . Начните с общего (пустого) определения, которое также служит базовым случаем для завершения рекрузии в поздней специализации:

template<typename ... T>
struct DataStructure {};

Это уже позволяет нам определить пустую структуру DataStructure<> data , хотя это еще не очень полезно.

Далее идет рекурсивная специализация случая:

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    DataStructure(const T& first, const Rest& ... rest)
        : first(first)
        , rest(rest...)
    {}
    
    T first;                                
    DataStructure<Rest ... > rest;
};

Теперь нам достаточно создавать произвольные структуры данных, такие как DataStructure<int, float, std::string> data(1, 2.1, "hello") .

Так, что происходит? Во-первых, обратите внимание, что это специализация, требование которой состоит в том, что существует хотя бы один параметр вариационного шаблона (а именно T выше), но не заботятся о конкретном составе пакета Rest . Знание того, что T существует, позволяет first определить его член данных. Остальные данные рекурсивно упаковываются как DataStructure<Rest ... > rest . Конструктор инициирует оба этих элемента, включая вызов рекурсивного конструктора для rest членов.

Чтобы понять это лучше, мы можем работать с примером: предположим, что у вас есть данные DataStructure<int, float> data . Объявление сначала совпадает со специализацией, DataStructure<float> rest структуру с DataStructure<float> rest int first и DataStructure<float> rest . rest определение снова совпадает с этой специализацией, создавая float first свои собственные float first и DataStructure<> rest . Наконец, последний rest совпадает с деформированием базового блока, создавая пустую структуру.

Вы можете визуализировать это следующим образом:

DataStructure<int, float>
   -> int first
   -> DataStructure<float> rest
         -> float first
         -> DataStructure<> rest
              -> (empty)

Теперь у нас есть структура данных, но она не очень полезна, но мы не можем легко получить доступ к отдельным элементам данных (например, чтобы получить доступ к последнему члену данных DataStructure<int, float, std::string> data нам пришлось бы использовать data.rest.rest.first , что не совсем удобно). Таким образом, мы добавляем к нему метод get (нужен только в специализации, так как базовая структура не имеет данных для get ):

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    ...
    template<size_t idx>
    auto get()
    {
        return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
    }
    ...
};

Как вы можете видеть это get функцию члена сама шаблонным — на этот раз по индексу элемента , который необходим ( и поэтому использование может быть что — то вроде data.get<1>() , аналогичен std::tuple ). Фактическая работа выполняется статической функцией в вспомогательном классе GetHelper . Причина , почему мы не можем определить необходимые функции непосредственно в DataStructure «s get потому , что (как мы скоро увидим) , мы должны были бы специализироваться на idx — но это не возможно специализироваться функциями — члена шаблона без Специализируя содержащий класс шаблон. Обратите также внимание на то, что использование C + + 14-стиля auto делает нашу жизнь значительно проще, поскольку в противном случае нам понадобится довольно сложное выражение для типа возврата.

Итак, на вспомогательный класс. На этот раз нам понадобится пустая декларация и две специализации. Сначала заявление:

template<size_t idx, typename T>
struct GetHelper;

Теперь базовый регистр (когда idx==0 ). В этом случае мы просто возвращаем first члена:

template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
    static T get(DataStructure<T, Rest...>& data)
    {
        return data.first;
    }
};

В рекурсивном случае мы уменьшаем idx и вызываем GetHelper для rest членов:

template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
    static auto get(DataStructure<T, Rest...>& data)
    {
        return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
    }
};

Для работы с примером предположим, что у нас есть данные DataStructure<int, float> data и нам нужен data.get<1>() . Это вызывает GetHelper<1, DataStructure<int, float>>::get(data) (вторая специализация), которая, в свою очередь, вызывает GetHelper<0, DataStructure<float>>::get(data.rest) , который, наконец, возвращает (по 1-й специализации, так как теперь idx равен 0) data.rest.first .

Итак, это все! Вот весь код функционирования, с некоторым примером используемого в main функции:

#include <iostream>

template<size_t idx, typename T>
struct GetHelper;

template<typename ... T>
struct DataStructure
{
};

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    DataStructure(const T& first, const Rest& ... rest)
        : first(first)
        , rest(rest...)
    {}
    
    T first;
    DataStructure<Rest ... > rest;
    
    template<size_t idx>
    auto get()
    {
        return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
    }
};

template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
    static T get(DataStructure<T, Rest...>& data)
    {
        return data.first;
    }
};

template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
    static auto get(DataStructure<T, Rest...>& data)
    {
        return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
    }
};

int main()
{
    DataStructure<int, float, std::string> data(1, 2.1, "Hello");
        
    std::cout << data.get<0>() << std::endl;
    std::cout << data.get<1>() << std::endl;
    std::cout << data.get<2>() << std::endl;
    
    return 0;
}

Явная реализация

Явное определение экземпляра создает и объявляет конкретный класс, функцию или переменную из шаблона, но пока не использует его. На явное создание можно ссылаться на другие единицы перевода. Это можно использовать, чтобы избежать определения шаблона в файле заголовка, если он будет создан только с помощью конечного набора аргументов. Например:

// print_string.h
template <class T>
void print_string(const T* str);

// print_string.cpp
#include "print_string.h"
template void print_string(const char*);
template void print_string(const wchar_t*);

Поскольку print_string<char> и print_string<wchar_t> явно создаются в print_string.cpp , компоновщик сможет их найти, даже если шаблон print_string не определен в заголовке. Если эти объявления с явным манифестом не присутствуют, вероятно, возникнет ошибка компоновщика. См. Почему шаблоны могут быть реализованы только в файле заголовка?

C ++ 11

Если явному описанию инстанцирования предшествует ключевое слово extern , оно вместо этого становится явным заявлением о создании. Наличие явной декларации о создании для данной специализации предотвращает неявное создание данной специализации в пределах текущей единицы перевода. Вместо этого ссылка на эту специализацию, которая в противном случае вызывала бы неявное создание, может ссылаться на явное определение инстанцирования в том же или другом TU.

foo.h

#ifndef FOO_H
#define FOO_H
template <class T> void foo(T x) {
    // complicated implementation
}
#endif

foo.cpp

#include "foo.h"
// explicit instantiation definitions for common cases
template void foo(int);
template void foo(double);

main.cpp

#include "foo.h"
// we already know foo.cpp has explicit instantiation definitions for these
extern template void foo(double);
int main() {
    foo(42);   // instantiates foo<int> here;
               // wasteful since foo.cpp provides an explicit instantiation already!
    foo(3.14); // does not instantiate foo<double> here;
               // uses instantiation of foo<double> in foo.cpp instead
}

Example

In contrast of a full template specialization partial template specialization allows to introduce template with some of the arguments of existing template fixed. Partial template specialization is only available for template class/structs:

// Common case:
template<typename T, typename U>
struct S {
    T t_val;
    U u_val;
};

// Special case when the first template argument is fixed to int
template<typename V>
struct S<int, V> {
    double another_value;
    int foo(double arg) {// Do something}
};

As shown above, partial template specializations may introduce completely different sets of data and function members.

When a partially specialized template is instantiated, the most suitable specialization is selected. For example, let’s define a template and two partial specializations:

template<typename T, typename U, typename V>
struct S {
    static void foo() {
        std::cout << "General casen";
    }
};

template<typename U, typename V>
struct S<int, U, V> {
    static void foo() {
        std::cout << "T = intn";
    }
};

template<typename V>
struct S<int, double, V> {
    static void foo() {
        std::cout << "T = int, U = doublen";
    }
};

Now the following calls:

S<std::string, int, double>::foo();
S<int, float, std::string>::foo();
S<int, double, std::string>::foo();

will print

General case
T = int
T = int, U = double

Function templates may only be fully specialized:

template<typename T, typename U>
void foo(T t, U u) {
    std::cout << "General case: " << t << " " << u << std::endl;
}

// OK.
template<>
void foo<int, int>(int a1, int a2) {
    std::cout << "Two ints: " << a1 << " " << a2 << std::endl;
}

void invoke_foo() {
    foo(1, 2.1); // Prints "General case: 1 2.1"
    foo(1,2);    // Prints "Two ints: 1 2"
}

// Compilation error: partial function specialization is not allowed.
template<typename U>
void foo<std::string, U>(std::string t, U u) {
    std::cout << "General case: " << t << " " << u << std::endl;
}

Понравилась статья? Поделить с друзьями:
  • Error function sahara rx data 237 unable to read packet header
  • Error function port connect 100 failed to open com port handle
  • Error function main 320 uploading image using sahara protocol failed
  • Error function main 303 uploading image using sahara protocol failed
  • Error function definitions are not permitted at the prompt or in scripts