C++20: Concepts, the Placeholder Syntax

Contents[Show]

Today, I have a simple answer to a challenging question: Where can I use my concept? Concepts can be used where auto is usable.

 TimelineCpp20Concepts

Before I write about the placeholder syntax and about the new way to define function templates, I have to make a detour. We have asymmetries in C++11/14.

From Asymmetries in C++11/14 to Symmetries in C++20

I often have discussions in my C++ seminars which goes like this.

What is the Standard Template Library from the birds-eyes perspective? Generic containers which can be manipulated with generic algorithms. The glue between these two disjunct components are iterators.

stl

 

Generic containers and generic algorithms mean that they are not bound to a specific type. Fine. Many of the algorithms can be parametrised by a callable. For example, std::sort has an overload which takes a binary predicate (callable). This binary predicate is applied to the elements of the container. A binary predicate is a function which takes two arguments and returns a boolean. You can use a function, a function object, or with C++11 a lambda as a binary predicate.

The Small Asymmetry in C++11

What's wrong with the following program? (I know, that we have the predefined predicate std::greater.)

// lambdaCpp11.cpp

#include <algorithm>
#include <iostream>
#include <string>
#include <array>
#include <vector>

template <typename Cont, typename Pred>
void sortDescending(Cont& cont, Pred pred){
    std::sort(cont.begin(), cont.end(), pred);
}

template <typename Cont>
void printMe(const Cont& cont){
    for (auto c: cont) std::cout << c << " ";
    std::cout << std::endl;
}

int main(){

    std:: cout << std::endl;
    
    std::array<int, 10> myArray{5, -10, 3, 2, 7, 8, 9, -4, 3, 4};
    std::vector<double> myVector{5.1, -10.5, 3.1, 2.0, 7.2, 8.3};
    std::vector<std::string> myVector2{"Only", "for", "testing", "purpose"};
    
    sortDescending(myArray, [](int fir, int sec){ return fir > sec; });           // (1)
    sortDescending(myVector, [](double fir, double sec){ return fir > sec; });    // (2)
    sortDescending(myVector2, [](const std::string& fir, const std::string& sec){ // (3)
       return fir > sec; 
    });

    printMe(myArray);
    printMe(myVector);
    printMe(myVector2);
    
    std::cout << std::endl;
    
}

 

The program has a std::array of int's, a std::vector of double's, and a std::vector of std::string's. All containers should be sorted in descending order and displayed. To ease my job, I create the two function templates sortDescending and printMe.

lambdaCpp11

Although containers and algorithms are generic, C++11 has only type-based lambdas. I have to implement the binary predicate for each data type (lines 1 - 3) and break, therefore, my generic approach.

With C++14, this asymmetry disappears. Containers, algorithms are generic and lambdas can be generic.

The Big Asymmetry in C++14

C++14 makes the implementation of the previous program lambdaCpp11.cpp straightforward.

// lambdaCpp14.cpp

#include <algorithm>
#include <iostream>
#include <string>
#include <array>
#include <vector>

template <typename Cont>
void sortDescending(Cont& cont){
    std::sort(cont.begin(), cont.end(), [](auto fir, auto sec){   // (1)
        return fir > sec; 
    });
}

template <typename Cont>
void printMe(const Cont& cont){
    for (auto c: cont) std::cout << c << " ";
    std::cout << std::endl;
}

int main(){

    std:: cout << std::endl;
    
    std::array<int, 10> myArray{5, -10, 3, 2, 7, 8, 9, -4, 3, 4};
    std::vector<double> myVector{5.1, -10.5, 3.1, 2.0, 7.2, 8.3};
    std::vector<std::string> myVector2{"Only", "for", "testing", "purpose"};
    
    sortDescending(myArray);      // (2)
    sortDescending(myVector);     // (2)
    sortDescending(myVector2);    // (2)

    printMe(myArray);
    printMe(myVector);
    printMe(myVector2);
    
    std::cout << std::endl;
    
}

Fine? Yes and No. Yes, because I can use a generic lambda (line 1) for each data-type in line 2. No, because I replaced one small asymmetry in C++11 with a bigger asymmetry in C++14. The small asymmetry in C++11 was, that lambdas are type-bound. The big asymmetry in C++14 is, that generic lambdas introduced a new syntactic form to write generic functions, better known as function templates. Here is the proof:

 

// genericLambdaTemplate.cpp

#include <iostream>
#include <string>

auto addLambda = [](auto fir, auto sec){ return fir + sec; }; // (1)

template <typename T, typename T2>                            // (2)
auto addTemplate(T fir, T2 sec){ return fir + sec; }

int main(){

    std::cout << std::boolalpha << std::endl;

    std::cout << addLambda(1, 5) << " " << addTemplate(1, 5) << std::endl;
    std::cout << addLambda(true, 5) << " " << addTemplate(true, 5) << std::endl;
    std::cout << addLambda(1, 5.5) << " " << addTemplate(1, 5.5) << std::endl;
    
    const std::string fir{"ge"};
    const std::string sec{"neric"};
    std::cout << addLambda(fir, sec) << " " << addTemplate(fir, sec) << std::endl;

    std::cout << std::endl;

}

 

The generic lambda in line 1 and the function template in line 2 produce the same results.

genericLambdaTemplate

This is the asymmetry in C++14. Generic lambdas introduce a new way to define function templates.

When I teach this in my seminars, I almost always get the question: Can we use auto in functions to get function templates? No with C++17 but yes with C++20.  In C++20, you can use constrained placeholders (concepts) or unconstrained placeholders (auto) in function declarations to get function templates. This means the asymmetry in C++ is gone with C++20.

The Solution in C++20

Before I write about the new way to define function templates, I want to answer my origin question: Where can I use my concept? Concepts can be used where auto is usable.

 

// placeholdersDraft.cpp

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

template<typename T>                                   // (1)
concept Integral = std::is_integral<T>::value;

Integral auto getIntegral(int val){                    // (2)
    return val;
}

int main(){

    std::cout << std::boolalpha << std::endl;
 
    std::vector<int> vec{1, 2, 3, 4, 5};
    for (Integral auto i: vec) std::cout << i << " ";  // (3)
 
    Integral auto b = true;                            // (4)
    std::cout << b << std::endl;

    Integral auto integ = getIntegral(10);             // (5)
    std::cout << integ << std::endl;

    auto integ1 = getIntegral(10);                     // (6)
    std::cout << integ1 << std::endl;

    std::cout << std::endl;

}

 

The concept Integral in line 1 can be used as a return type (line 2), in a range-based for-loop (line 3), or as a type for the variable b (line 4) or the variable integ (line 5). To see the symmetry, line 6 uses type-deduction with auto instead. I have to admit that there is now no compiler available, which can compile completely the proposed concepts syntax which I used in my example. The following output is, therefore, from GCC and the previous Concepts Technical Specification (Concepts TS).

conceptsPlaceholder

What's next?

My next post is about the syntactic sugar, we get with constrained placeholders (concepts) and unconstrained placeholders (auto) in C++20. I assume, the function (template) getIntegral gives you a first impression.

 

 

Thanks a lot to my Patreon Supporters: Paul Baxter,  Meeting C++, Matt Braun, Roman Postanciuc, Venkata Ramesh Gudpati, Tobias Zindl, Marko, G Prvulovic, Reiner Eiteljörge, Reinhold Dröge, Abernitzke, Richard Ohnemus, Frank Grimm, Sakib, Broeserl, António Pina, Markus Falkner, Darshan Mody, Sergey Agafyin, Андрей Бурмистров, and Jake.

 

Thanks in particular to:   crp4

 

   

Get your e-book at Leanpub:

The C++ Standard Library

 

Concurrency With Modern C++

 

Get Both as one Bundle

cover   ConcurrencyCoverFrame   bundle
With C++11, C++14, and C++17 we got a lot of new C++ libraries. In addition, the existing ones are greatly improved. The key idea of my book is to give you the necessary information to the current C++ libraries in about 200 pages. I also included more than 120 source files.  

C++11 is the first C++ standard that deals with concurrency. The story goes on with C++17 and will continue with C++20.

I'll give you a detailed insight in the current and the upcoming concurrency in C++. This insight includes the theory and a lot of practice with more than 140 source files.

 

Get my books "The C++ Standard Library" (including C++17) and "Concurrency with Modern C++" in a bundle.

In sum, you get more than 700 pages full of modern C++ and more than 260 source files presenting concurrency in practice.

 

Get your interactive course

 

Modern C++ Concurrency in Practice

C++ Standard Library including C++14 & C++17

educative CLibrary

Based on my book "Concurrency with Modern C++" educative.io created an interactive course.

What's Inside?

  • 140 lessons
  • 110 code playgrounds => Runs in the browser
  • 78 code snippets
  • 55 illustrations

Based on my book "The C++ Standard Library" educative.io created an interactive course.

What's Inside?

  • 149 lessons
  • 111 code playgrounds => Runs in the browser
  • 164 code snippets
  • 25 illustrations

Add comment


My Newest E-Books

Course: Modern C++ Concurrency in Practice

Course: C++ Standard Library including C++14 & C++17

Course: Embedded Programming with Modern C++

Course: Generic Programming (Templates)

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 3749

All 3383760

Currently are 276 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments