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 can be manipulated with generic algorithms. The glue between these two disjunct components is 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 that takes a binary predicate (callable). This binary predicate is applied to the elements of the container. A binary predicate is a function that 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 original 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: Matt Braun, Roman Postanciuc, Tobias Zindl, Marko, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, espkk, Louis St-Amour, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Neil Wang, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Tobi Heideman, Daniel Hufschläger, Red Trip, Alexander Schwarz, Tornike Porchxidze, Alessandro Pezzato, Evangelos Denaxas, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Satish Vangipuram, and Michael Dunsky.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, and Said Mert Turkal.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

Seminars

I'm happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Bookable (Online)

German

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

New

Contact Me

Modernes C++,

RainerGrimmSmall

 

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)

Course: C++ Fundamentals for Professionals

Interactive Course: The All-in-One Guide to C++20

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 4411

Yesterday 7071

Week 41387

Month 202061

All 6850753

Currently are 199 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments