22
loading...
This website collects cookies to deliver better user experience
template <typename T>
struct Base
{
void baseMethod()
{
std::cout << "Base<T>::f()\n";
}
};
template <typename T>
struct Derived : Base<T>
{
void derivedMethod()
{
std::cout << "Derived<T>::g()\n ";
/**
* ERROR: Dependent Name (as there is no baseMethod() in derived class): Call to baseMethod() depends
* on Base class template hence compiler is not aware of baseMethod().
*/
baseMethod();
f(); // Non dependent Name: hence no issues here
}
void f()
{
}
};
int main()
{
Derived<int> d{};
d.derivedMethod();
return EXIT_SUCCESS;
}
/* ERROR
test.pp.cpp: In member function 'void Derived<T>::derivedMethod()':
test.pp.cpp:21:20: error: there are no arguments to 'baseMethod' that depend on a template parameter, so a declaration of 'baseMethod' must be available [-fpermissive]
baseMethod();
^
test.pp.cpp:21:20: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)
*/
baseMethod()
as a non-dependent name, and must be resolved at the point of template's definition. At this point compiler doesn't know Base<T>::baseMethod()
as it can be specialized later. A simple fix is to make the compiler understand that the call baseMethod()
depends on template parameters. Changing the call to baseMethod()
as Base<T>::baseMethod()
(or this->baseMethod()
) would compile the code without errors as the call to function baseMethod()
is resolved at the point of template's instantiation.template <typename T>
struct Base
{
using value_type = T;
void baseMethod()
{
std::cout << "Base<T>::f()\n";
}
};
template <typename T>
struct Derived : Base<T>
{
value_type val = 10; //(1) ERROR: 'value_type' is not declared in the scope
Base<T>::value_type val = 10; //(2)ERROR: need 'typename' before 'Base<T>::value_type' because 'Base<T>' is a dependent scope
typename Base<T>value_type val = 10; //(3) Works
void derivedMethod()
{
std::cout << "Derived<T>::g()\n ";
Base<T>::baseMethod();
}
};
(1)
doesn't work (value_type
is non-dependent); when we use typename
in(3)
we are explicitly telling the compiler that it is a type. This is stated in the C++ standard, section 14.6:A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename
.
// Example for template template parameter used with class
template <typename T, template <typename, typename> class Cont > // the keyword class is a must before C++17, otherwise typename can also be used
class MyContainer
{
public:
explicit MyContainer(std::initializer_list<T> inList): data(inList)
{
}
int getSize() const
{
return data.size();
}
void printCont()
{
for(const auto& d: data)
{
std::cout << d << ' ';
}
std::cout << '\n';
}
private:
Cont<T, std::allocator<T>> data; // the hidden default allocator in STL should be explicitly defined with the container to work with templates.
};
int main()
{
MyContainer<int, std::vector> myIntVec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::cout << "myIntVec.getSize(): " << myIntVec.getSize() << std::endl;
return EXIT_SUCCESS;
}
/*OUTPUT
myIntVec.getSize(): 10
*/
T
, is the name of a type. The second parameter Cont
, is a template template parameter. It's the name of a class template that has a two typename
parameter. Note that we didn't give a name to the typename
parameter of Cont, although we could have:template <typename T, template <typename ElementType, typename Allocator> class Cont>
class MyContainer;
ElementType
and Allocator
above) can serve only as documentation. These names are commonly omitted, but you should feel free to use them where you think they improve readability. For additional convenience, we can employ a default for the template template argument.template <typename T, template <typename, typename> class Cont = std::queue>
class MyContainer
{
//...
};
//...
MyContainer<int> c1; // use default: Cont is std::queue
MyContainer<std::string, std::list> c2; // Cont is std::list
template <typename T, template <typename, typename> class Cont >
void print_container(Cont<T, std::allocator<T> > container)
{
for (const T& v : container)
std::cout << v << ' ';
std::cout << '\n';
}
int main()
{
std::vector<char> v{'c','+','+'}; //initilize vector
std::list<int> lt(5,10); // initializing a list with 5 elements
std::cout << "Vector elements: ";
print_container(v);
std::cout << "List elements: ";
print_container(lt);
return EXIT_SUCCESS;
}
/*OUTPUT
Vector elements: c + +
List elements: 10 10 10 10 10
/*
template <
template <typename, typename> class Cont1,
template <typename, typename> class Cont2,
typename T>
void print_container(Cont1<Cont2<T, std::allocator<int>>, std::allocator<Cont2<T, std::allocator<T>>>> container)
{
for (const auto &c2 : container)
{
for (const auto &v : c2)
{
std::cout << v << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
}
int main()
{
std::vector<std::vector<int>> vec{{1, 2, 3}, {4, 5, 6}}; //initilize vector
std::cout << "Vector elements: \n";
print_container(vec);
return EXIT_SUCCESS;
}
/*OUTPUT
Vector elements:
1 2 3
4 5 6
*/
/************** Breaking down function argument for better understanding ******************
*
* using T1 = Cont2<T, std::allocator<int> >, then the argument would be
*
* Cont1<T1, std::allocator<T1>>
*/
template<typename T>
void outerMethod(T& param)
{
innerMethod(param);
}
outerMethod()
accepts an lvalue reference, therefore we can only pass lvalues to it.int x = 10;
outerMethod(x); // Works, lvalue is passed as argument
outerMethod(10) // ERROR: passing rvalue to lvalue reference
outerMethod()
to accept const
lvalue reference, then the innerMethod()
would not be allowed to modify the argument. We would have to overload the outerMethod()
to handle rvaluestemplate<typename T>
void outerMethod(T&& param)
{
innerFunction(param);
}
innerFunction()
must accept the rvalue reference as an argument. We know that, even if the outer function accepts an rvalue reference when it comes to passing that argument to the inner function it will be seen by the compiler as lvalue and is therefore not allowed. This is where perfect forwarding or Forwarding reference comes to our rescue.T
of a template function argument.T& &
becomes T&
T& &&
becomes T&
T&& &
becomes T&
T&& &&
becomes T&&
std::forward
is required for solving the perfect forwarding problem with the functions purpose to resolve that awkward rule in which rvalue references are treated as lvalues.class MyClass
{
public:
MyClass(std::string b) : b(b) {} // Copy Constructor
MyClass(const MyClass &other) : b(b)
{
b = other.b;
std::cout << "Copy Constructor\n";
}
MyClass(MyClass &&other) // Move Constructor
{
b = std::move(other.b);
std::cout << "Move Constructor\n";
}
private:
std::string b;
};
// And a template function
template <typename T>
void OuterFunction(T &¶m)
{
// As per the rule (third and fourth) lvalue evaluates to lvalue and rvalue evaluate to rvalue
MyClass a(std::forward<T>(param));
}
int main()
{
// Passing an lvalue
MyClass a = MyClass("Amar");
OuterFunction(a);
// Passing an rvalue
OuterFunction(MyClass("Akbar"));
}
/*OUTPUT
Copy Constructor
Move Constructor
*/
π
; which needs to be defined for various numeric types (e.g., int
, float
, double
) to handle different precisions.template <typename T>
constexpr T pi = T(3.1415926535897932385);
pi
constant for different numeric types.std::cout << pi<int> << std::endl;
std::cout << pi<double> << std::endl;
template<size_t T> struct fact;
template<> // Explicit specialization
struct fact<0>
{
constexpr static auto value = 1;
};
template<size_t T>
struct fact
{
constexpr static auto value = T * fact<T - 1>::value;
};
static_assert(fact<0>::value == 1);
static_assert(fact<1>::value == 1);
static_assert(fact<2>::value == 2);
static_assert(fact<3>::value == 6);
static_assert(fact<4>::value == 24);
static_assert(fact<5>::value == 120);
NOTE: The static_assert
throws error during compile time if the factorial of a number doesn't match.
using identifier = type-id
template<template-params-list> identifier = type-id // to alias templates
template <typename T>
using vec_t = std::vector<T, custom_allocator<T>>;
vec_t<int> vi; // std::vector<int, custom_allocator<int>>
vec_t<std::string> vs; // std::vector<std::string, custom_allocator<std::string>>
template<typename T>
using ptr = T*;
ptr<double> p = new double; // double* p = new double;
f()
for a variable number of arguments of different types:template<typename T, typename... Tail>
void f(T head, Tail... tail)
{
g(head); //do someting to head
f(tail...); //try again with tail
}
void f() { } //do nothing
g()
) for head
, the function f()
is recursively called with the rest of the arguments (tail
).The ellipses ...
is used to indicate the rest of a list. when the tail become empty and we need a separate function to deal with that.f()
as f(1.5, 23, "Amar");
, the head
(1.2
) is precessed by g()
and later call f(23, "Amar");
with rest parameters, which will recursively call f("Amar");
, which will call empty function f()
.f(1.5, 23, "Amar") calls --> f(23, "Amar") calls --> f("Amar") calls --> f()
template<typename T>
T multiply(const T& arg)
{
return arg;
}
template<typename T, typename... ARGS> // Function parameter pack
T multiply(const T& arg, const ARGS&... args)
{
return arg * multiply(args...); // Unpacking the parameter
}
int main()
{
std::cout << multiply(1, 5u, 6u, 8L);
}
/*OUTPUT
240
*/
template <typename... T_values>
class Base
{
public
virtual void f(T_values... values) = 0;
};
class Derived1 : public Base<int, short, double>
{
public:
void f(int a, short b, double c) override;
};
class Derived2 : public Base<std::string, char>
{
public:
void f(std::string a, char b) override;
};
typename... T_values
is called template parameter pack. if you are just templating a function then it's called a function parameter pack.class MyClass
{
public:
template <typename... T_values>
void myMethod(T_values... values);
};
T_values...
in myMethod()
signature is unpacking the parameter pack in function parameter list. template <typename... Args>
struct PrintType;
template <typename First, typename... Args>
struct PrintType<First, Args...>
{
static std::string name()
{
return std::string(typeid(First).name()) + " " + PrintType<Args...>::name();
}
};
// Need partial specialization to end the recursion
template <> // Partial specialization
struct PrintType<>
{
static std::string name()
{
return "";
}
};
template <typename... Args>
std::string type_name()
{
return PrintType<Args...>::name();
}
int main()
{
std::cout << type_name<bool, char, int , double>() << std::endl;
return 0;
}
/*OUTPUT
b c i d
*/
template <typename... Args>
void outerMethod(Args&&... args) {
innerMethod(std::forward<Args>(args)...);
}
NOTE: Forwarding references can only be used for template parameters
template< typename T>
struct MyStruct
{
private:
T* cont;
size_t size;
public:
MyStruct(std::initializer_list<T> list): cont(new T[list.size()]), size(list.size())
{
int i = 0;
for(auto &l: list)
{
*(cont + i++) = l;
}
}
void printCont()
{
for(int i = 0; i < size; i++)
{
std::cout << *(cont + i) << ' ';
}
std::cout << '\n';
}
};
template< template<typename, typename...> class ContainerType, typename Type, typename... Types>
auto build_container(Type first, Types... args)
{
ContainerType<Type> c{first, args...};
return c;
}
int main()
{
using namespace std::string_literals;
auto v = build_container<MyStruct> ("Amar"s, "Akbar"s, "Anthony"s, "Arjun"s);
v.printCont();
}
/*OUTPUT
Amar Akbar Anthony Arjun
*/
typename
and class
are replaceable in most of the cases. However, in C++, sometimes you must use typename.struct Entity
{
using SubType = int;
//...
};
template <class T>
class MyClass
{
typename T::SubType type; // keyword typename is used as the identifier before the type.
// ...
};
// Another example
class ClassA
{
public:
class foo
{
};
};
template<typename C>
class ClassB : public C::foo // dependent name, typename keyword is a must
{
};
typename
when a dependent type occurs. To Specify Template Template Type we should use class
keyword (prior to C++17).template< template<typename, typename...> class ContainerType, typename Type, typename... Types>
auto build_container(Type first, Types... args)
{
ContainerType<Type> c{first, args...};
return c;
}
22