C++20 Concepts: part 4

In this issue of the concepts tutorial, I’ll discuss in detail about abbreviated function template syntax , constraining auto with abbreviated function template syntax , constraining deduced return type, i.e. constraining the auto return type with concepts.

1. Abbreviated function template syntax in C++20

1.1 Introduction

With the introduction of concepts in C++20, generic program (and here function templates) have changed in meaning and specification. Abbreviated function templates are a part of the change.

C++20 abbreviated function templates in C++20 mean the same to generic functions, what generic lambda expressions in C++14 meant: use of auto in function parameters instead of writing the verbose template syntax . For e.g.

template <typename T>
void foo(T param) { /* do something */ } (1)

in C++20 can be written as

void foo(auto param) { /* do something */ } (2)

without changing the meaning of the function. Here’s what cpp reference page have to say about abbreviated function template syntax:

When placeholder types (either auto …) appear in the parameter list of a function declaration or of a function template declaration, the declaration declares a function template …

which simply means that the definitions (1) and (2) are equivalent. This applies to all kinds of template declarations including variadic templates.

This declaration may remind one of the generic lambdas which were introduced in C++14 which allowed auto in the parameter list of the lambda definition and were further enhance in C++20 with the introduction of template syntax in lambda expression .

1.2 Difference between non-type template with auto and abbreviated template syntax

Abbreviated function template syntax, as the name suggest are very different from the non-type template parameter with auto syntax, a feature that was introduced in C++17 and allowed for definition of generic C++ objects and not functions with auto .

For e.g a struct Foo that could be used to print out the values of non-type template parameter would look like below:

template<auto p>
class Foo{
using P_t = decltype(P); //define the type of P

//store the template parameter with P_t
P_t p;
public: auto operator()(){
std::cout << "Foo: " << p << "\n";
}
};int main(){ Foo<1> f1; //non-type parameter
f1();
Foo<'c'> f2;
f2();
return 0;
}

What is more interesting is the limitations of non-type parameters that can be deduced by auto or allowed as parameters for specialization.

To not confuse my readers at this point, I’ll decide to write a dedicated article to cover non-type template parameters for methods and classes and all updates till C++20 in the coming weeks.

2 Constrained auto

2.1 Constrained auto as function parameter

As easy and fancy the abbreviated function template syntax seems, it comes with a problem, namely that any argument could be passed to such function accepting unconstrained template. Consider again a method drawShape that can draw 2-D geometric shapes of any kind. In our case lets simply try to stream on the console xxx drawn! , where xxx could be square, circle, ellipse etc.

namespace Shape2DLib
{
struct Shape2D{
virtual void draw() = 0;
};
struct Square: Shape2D{
void draw() override{ std::cout << "square drawn!\n";}
};
struct Circle: Shape2D{
void draw() override{std::cout << "circle drawn!\n";}
};
} //end of namespace Shape2DLib
void drawShape(const auto& shape){ shape.draw();}/* same as
template<typename ShapeType>
void drawShape(const ShapeType shape);
*/

By looking at the definition of drawShape , it is clear that the parameter shape should provide draw method which doesn’t take any arguments. But for library code, this might not be immediately obvious and someone comes along and provides a custom shape Ellipse to be used with drawShape

//Shape2DLib doesn't provide Ellipse 
// shape, so a user provides it
struct Ellipse{
// ...
/* user did not know the requirements of drawShape
and hence did not provide "drawShape" method
*/
};

A client code to draw shapes may look like below

int main(){
Shape2DLib::Square s1;
Shape2DLib::Circle c1;
Ellipse e1
drawShape(s1); //OK
drawShape(c1); //OK
drawShape(e1); //oops, compiler error after e1.draw()
}

and an unconstrained auto function parameter may lead to insidious compiler errors if the requirements on parameters are not respected (which has been stressed over and over again in the last three articles!).

The crux of the discussion is that abbreviated function template syntax has pitfalls of the classic generic code. The good news is that this construct was permitted since C++20 to be used with concepts and constrain auto just like the template parameters are constrained. For e.g. constraining the drawShape the following concept may be deployed:

template<typename T>
concept Shape_t = requires(const T& shape)
{
{ shape.draw() }; -> std::same_as<void>;
//constrain 2 ...
//constrain 3 ...
};

the drawShape can be defined with a very small modification, namely placing the concept Shape_t before auto :

void drawShape(const Shape_t auto& shape){ shape.draw();}

and this again translates to the generic function template syntax with constrained types

template<typename Shape_t>
void drawShape(const Shape_t& shape){ shape.draw();}

but without the verbose template syntax

2.2 Constrained auto as return type

The Problem
Since auto as function return type has existed since C++14, it can also be constrained since C++20 similar to passing constrained parameters with auto as explained above. For e.g consider a function getShape using a deduced return type auto where the author forgets to add return statement in the body of the function:

namespace Shape2DLib{
auto
getShape()
{
//do something
...
...
// forgot to return ??, but code compiles w/o warnings
}
}

Later in a client code someone tries to use getShape() method :

int main(){    
//later somewhere else
auto sh = Shape2DLib::getShape(); //oops 'void sh' has
//incomplete type error
}

the error message might leave the author scratching her/his head until after scavenging through the web and realizing that the library method getShape actually doesn’t return anything.

Worse it might return something but not what it says it should. For e.g.

namespace Shape2DLib{
auto
getShape()
{
//do something
int someNum{1};
std::vector<float> vec{1,2,3,4,5};
//do some more things

return vec; //ok
//return someNum; //also ok
}
}//end of namespace Shape2DLib

int main(){
//later somewhere else
auto sh = Shape2DLib::getShape(); //also compiles, now user code inherits a bug
}

Such insidious bugs may never get exposed unless the library authors have written tests, reviewed the code and followed bunch of best practices. But errors may still happen even in the ideal world.

The Solution
To prevent such error in generic functions, or class methods, the deduced return type with auto can also be constrained which will result in compilation error when author forgets to return or returns something that is not intended. For e.g.:

namespace Shape2DLib{
Shape_t auto
getShape()
{
//do somehthing
int num{0};
std::vector<float> vec{1,2,3,4,5};

//return num; //compiler error: num doesn't have "draw()" (1)
//return vec; //same error as above (2)
Shape2DLib::Square sq;
Shape2DLib::Circle cir;
Ellipse el;
return sq; //OK //return cir; //Ok //return el; //same error as (1), (2)
}

One can also conveniently use the constrained auto with trailing return type:

namespace Shape2DLib{
auto
getShape() -> Shape_t auto
{
...
}
}

3 Conclusion

In my opinion, the abbreviated function template syntax and constrained auto are the easiest parts of concepts and can be used with ease. These feature also provides a solution for the proponents against usage of auto as return type in functions and class methods.

I hope you enjoyed learning about the latest updates the confluence of generic functions, deduced return types and concepts in C++20. Stay tuned for more articles about C++20 concepts.

Support me by becoming a member

If you like my tutorials and articles, please consider supporting me by becoming a member through my medium referral link and get unlimited access to all articles on medium.com for just $5 a month.

--

--

--

I'm a backend software developer and from time to time I also like to explore web development

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Mastering Python Fundamental in 3 days — Day 1 Python Installation

10 Best Web Development Frameworks

Flask web application roadmap

What I Have Learned at Encora — 6

Making your own simple shell in c

When you begin working at 85%, you may have to turn your rearview mirror toward

Ben Landis http://bit.ly/2kB0ZIL Thanks for Following us on Twitter!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Gajendra Gulgulia

Gajendra Gulgulia

I'm a backend software developer and from time to time I also like to explore web development

More from Medium

C++20 Concurrency: part-3 request_stop and stop_token for std::jthread

C++ Templates: What is std::enable_if and how to use it?

Friendly Introduction Pointers in C++

Modern C++ in Advent of Code: Day2