Concepts - Placeholders

Contents[Show]

C++11 has with auto unconstrained placeholders. You can use concepts in C++20 as constrained placeholders. What seems at first glimpse not so thrilling is for me the decisive quantum leap. C++ templates will become an easy to use C++ feature.

Before I present the new syntax, I have to make a short remark. After my research to concepts and my experiments with unconstrained and constrained placeholders, I'm very biased. Therefore you can not expect an quite objective post.   

An ever and ever-recurring question

I hear often in my C++ and Python seminars the question: When is a programming language easy? Of course, the answer can not be that a programming language is easy if you can solve difficult questions in an easy way. That is a contradiction.

For me, a programming language is easy if you can reduce it to a few simple principles. I call such a principle a red thread. I hope you get the German proverb. The idea of these few simple principles is that you can deduce the features of the language from these principles. According to my definition, Python is a simple programming language. For example, if you get the idea of building slices on a sequence, you can apply this principle in many contexts.

slice

Therefore, the syntax will follow the same principle if I want to return each third element of a just-in-place created range range(0,10,3), a string,  a list, or a tuple. The same principle will hold if I return the second element of a just-in-place created range range(9,0,-2),  a string, a list or a tuple in reverse order.

According to my definition, C++98 is not a simple language. C++11 is something in between. For example, we have rules such as you can initialize all with curly braces (see { } - Initialization). Of course, even C++14 has a lot of features where I miss a simple principle. One of my favourites is the generalised lambda function.

1
2
3
4
5
6
auto genLambdaFunction= [](auto a, auto b) { return a < b; };

template <typename T, typename T2>
auto genFunction(T a, T2 b){
  return a < b;
}

 

By using the placeholder auto for the parameter a and b the generalised lambda function becomes in a magic way a function template. (I know, genLambdaFunction is a function object that has an overloaded call operator which accepts two type parameters.). genFunction is also a function template but you can not just define it by using auto. Hence you have to use a lot more syntax (line 3 and 4). That is syntax which is often too difficult for a lot of C++ programmer. 

Exactly that asymmetry will be removed with the placeholder syntax. Therefore, we have a new simple principle and C++ will become - according to my definition - a lot easier to use.

Placeholders

We will get unconstrained and constrained placeholders. auto is a unconstrained placeholder because a with auto defined variable can be of any type. A concept is a constrained placeholder because it can only be used to define a variable that satisfies the concept. I introduced concepts in the post Concepts with the help of Haskells typeclasses. I got international praise and blame for my approach.

Let me define and use a simple concept before I dig into the details.

A simple concept

Thanks to the concept Integral, the arguments of my gcd algorithm have to be integrals.

 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
30
// conceptsIntegral.cpp

#include <type_traits>
#include <iostream>

template<typename T>
concept bool Integral(){
  return std::is_integral<T>::value;
}

template<typename T>
requires Integral<T>()
T gcd(T a, T b){
  if( b == 0 ){ return a; }
  else{
    return gcd(b, a % b);
  }
}

int main(){

  std::cout << std::endl;

  std::cout << "gcd(100, 10)= " <<  gcd(100, 10)  << std::endl;
  std::cout << "gcd(100, 33)= " << gcd(100, 33) << std::endl;
  // std::cout << "gcd(5.5, 4,5)= " << gcd(5.5, 4.5) << std::endl;

  std::cout << std::endl;

}

 

I define in line 6 the concept  Integral. The concept Integral will evaluate to true if the predicate std::is_integral<T>::value returns true for T. std::is_integral<T> is a function of the type-traits library. The functions of the type-traits library enable amongst other things that you can check types at compile time. You can read the details about the type-traits in the posts about the type-traits library.  In particular,  I used the functions of the type-traits library to make the gcd algorithm more and more type-safe: More and More Save. I applied the concept in line 12. I will write in my next post how you can apply a concept in a more simpler way. Therefore, the border between function templates and function successively distinguish.

But now, back to my small example. Thanks to the relatively new GCC 6.3 and the compiler flag -fconcepts, I can compile and run the program.

conceptsIntegral

What will happen if I use the line 26? The concept kicks in.

conceptsIntegralError

Once more, back to the placeholders. To be specific, constrained and unconstrained placeholders.

Constrained and unconstrained placeholders

You can use constrained placeholders (concepts) in each situation where you can use unconstrained placeholders (auto). If this is not an intuitive rule?

 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
30
31
32
33
34
35
// conceptsPlaceholder.cpp

#include <iostream>
#include <type_traits>
#include <vector>

template<typename T>
concept bool Integral(){
  return std::is_integral<T>::value;
}

Integral getIntegral(auto val){
  return val;
}

int main(){
  
  std::cout << std::boolalpha << std::endl;
  
  std::vector<int> myVec{1, 2, 3, 4, 5};
  for (Integral& i: myVec) std::cout << i << " ";
  std::cout << std::endl;  

  Integral b= true;
  std::cout << b << std::endl;
  
  Integral integ= getIntegral(10);
  std::cout << integ << std::endl;
  
  auto integ1= getIntegral(10);
  std::cout << integ1 << std::endl;
  
  std::cout << std::endl;

}

 

For simplicity reasons, I reuse the concept Integral in line 7 - 10. Hence I iterate over integrals in the range-based for-loop in line 21 and my variable b in line 24 has to be integral. My usage of concepts goes on in line 27 and 30. I require in line 27 that the return type of getIntegral(10) has to fulfil the concept Integral.  I'm not so strict in line 30. Here I'm fine with an unconstrained placeholder.

At the end, as ever, the output of the program. There was no surprise. Concepts behave totally intuitive.

conceptsPlaceholder

That's the end of my post. Of course, it's not! I guess the most of you didn't recognise that I secretly introduced a new key feature of placeholders. Have a close look at the function getIntegral (line 12).

Integral getIntegral(auto val){
  return val;
}

The concept Integral as the return type is quite easy to get because it's possible to use unconstrained placeholders as return type since C++11. With C++20, we can use - according to the simple rule - constrained placeholders. My point is a different one. I use auto for the type of the parameter. That is only possible for generalised lambda functions (see the first example). A generalised lambda function is under the hood a function template. Now, I will come back to my red thread. getIntegral becomes due to the auto parameter a function template.  That is happening without the usual function template syntax. getIntegral accepts arbitrary types and returns only values of a type that fulfils the concept Integral.

What's next?

In the next post, I will continue my story about placeholders because the unification of templates, concepts, and placeholders goes on.

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

Add comment


My Newest E-Books

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 14

All 383773

Currently are 204 guests and no members online