β Welcome π to my ππ π Christmas ππ πβ¨ Specialβ¨ ββ!
Today I present you my personal TOP 5 most astounding C++20 syntax constructs.
Hope you will enjoy and maybe will see something new and/or interesting.
Here it goes:
No. 5:
[](){}();
This line creates an empty lambda and directly executes it. Effectively, you can consider it as a no-op. Can an empty lambda be used for something useful?
Yes, it can. I will illustrate one useful example. An empty lambda could be useful as a no-op default to store in a std::function. Since every lambda has its own distinct type, std::function is one of the first choice for store a lambda which could change at runtime.
// first create std::function without a default.
std::function<void()> f_null;
// f_null contains a nullptr now!
if( f_null ) { // calls operator bool()
f_null(); // without the if check this would throw!
}
// now create a std::function with a no-op default.
std::function<void()> f_default( []() {} );
f_default(); // can be called unconditionally! :)
struct Test
{
// in real world code this would be private ...
std::function<void()> mMemFun = [](){};
void Call() const
{
mMemFun(); // can call unconditionally.
}
};
Test test; // default construct with no-op lambda.
test.Call();
test.mMemFun = []() { puts( "Test from TOP No. 5." ); };
test.Call();
This post was viewed [wpstatistics stat=pagevisits time=total id=926] times.
No. 4:
explicit Constructor( X x, Y y, Z z )
try : mMember1( x )
, mMember12( y, z )
{
} catch( ... ) {
}
This is a Function try block. The try block includes the initializer list!
This is useful if in the middle of the member initialization an exception is thrown and the half created class instance need to do some clean up. There is one special behavior in this construct. Do you know which?
The catch block will always re-throw the exception automatically because the class instance was not finally created and stays broken.
class Foo
{
std::string mStr;
public:
// Constructor
explicit Foo( std::string const & s, size_t pos )
// # 4: Function try block. The try block includes the initializer list!
try : mStr( s, pos )
{
puts( "Foo constructor - normal block." );
} catch( ... ) {
// here can only cleanup already initialized/allocated stuff, which must be clean up manually.
// Constructed members are destructed already.
// the class instance itself stays broken. this catch block will auto re-throw the exception always!
puts( "Foo constructor - catch block!." );
}
};
try {
std::string s = "test";
Foo foo1( s, 2 ); // not throw
Foo foo2( s, 10 ); // will throw std::out_of_range
} catch( ... ) {
}
No. 3:
template<typename T>
explicit Constructor( long long x ) noexcept(noexcept(T(x)))
// ...
The noexcept( noexcept() )
. The inner noexcept is the noexcept operator, the outer is the noexcept specifier.
This construct is useful if the noexcept specification is dependent on some template specializations and could possibly vary between throwing and non-throwing.
class Throw
{
long long z = 23;
public:
// this constructor might throw
explicit Throw( long long x )
: z( x )
{
// just pick some condition when throw...
if( x % 2 ) throw std::runtime_error( "Throw" );
}
};
class NoThrow
{
long long a = 42;
public:
// this constructor is noexcept - never throws
NoThrow( long long x ) noexcept
: a(x)
{
}
};
template< class T>
class Foo
{
T mT;
public:
// # 3: the noexcept( noexcept() ), the inner noexcept is the noexcept operator, the outer is the noexcept specifier.
explicit Foo( long long x ) noexcept(noexcept(T(x)))
: mT(x)
{
std::cout << "Foo is noexcept " << std::boolalpha << noexcept(Foo( x )) << std::noboolalpha << std::endl;
}
};
Foo<Throw> foo1(4LL); // foo1 may throw....
Foo<NoThrow> foo2(101LL); // foo2 never throws.
No. 2:
template<typename T>
T mul( T const & t ) requires requires { t * t; }
{ /* ... */ }
The requires requires. First is the requires constraint, second the ad-hoc requires expression (instead of an already usable concept).
With this you can specify an ad-hoc constraint without prior creating a concept. A method of a class could be made available only for specific template specializations.
template< class T >
class Bar
{
T mT;
public:
Bar( T t )
: mT(t)
{ }
// # 2: requires requires, first is the requires constraint, second the ad-hoc requires expression (instead of an already usable concept)
T mul( T const & t ) requires requires { t * t; }
{
// t has the binary operator*()
return mT * t;
}
};
Bar bar1( 2 ); // T is int
std::cout << "bar1.mul(3) == " << bar1.mul( 3 ) << std::endl; // compiles, int has * operator
Bar bar2( std::string("test") ); // now T is std::string
// will not compile, std::string has no operator *
// error C7500: 'mul': no function satisfied its constraints
//std::cout << "bar2.mul(test 2) == " << bar2.mul( std::string("test 2") ) << std::endl;
And finally β¦
β¦ the β¦
β¦ ultimate and most astounding β¦
No. 1:
auto res = lambda_value.operator()< int >( 1LL );
Look closely. There are parentheses, then angle brackets and then parentheses again.
I judge this example to be the top most astounding syntax, because I discovered its existence by my self in my own real-world code. The interesting part is, that behind this construct is a lambda function and it is only functional when you call it this way β yes, a lambda function. Before my discovery I did not imagine that a lambda function can and must be called this way.
This construct is necessary if you want, e.g., specify an explicit return type but the return type can be different for each invocation.
Then a lambda construct and usage could be like in the following example code:
// first construct the lambda. (The construct itself could also be judged as No. 1 astounding syntax, couldn't it? ;-) )
auto lambda_value = []<typename RET>( auto n ) noexcept
{
if( n < std::numeric_limits<RET>::min() ) {
return std::numeric_limits<RET>::min();
} else if( n > std::numeric_limits<RET>::max() ) {
return std::numeric_limits<RET>::max();
} else {
return static_cast<RET>(n);
}
};
// # 1: explicit template specialization of the operator () of a lambda!
auto res = lambda_value.operator()< int >( 1LL ); // param is long long, return type is explicit set to int.
std::cout << "lambda_value.operator()< int >( 1LL ) == " << res << std::endl;
auto res2 = lambda_value.operator()< signed char >( 129LL ); // param is long long, return type is explicit set to signed char.
std::cout << "lambda_value.operator()< signed char >(129LL) == " << static_cast<int>(res2) << std::endl;
My real-world example from my own code is from a Unittest of TeaScript.
There I also had constructs which may vary in their return type (e.g., the return type of an evaluated TeaScript expression/statement) and I needed to encapsulate some code in a reusable lambda function for ease my coding, readability and faster progress.
auto eval_value = []<typename Ret>( teascript::Parser &p, teascript::Context &c, auto content ) {
p.ClearState();
return p.Parse( content, "_TEST_" )->Eval( c ).GetValue<Ret>();
};
// and then I used it like this:
BOOST_CHECK_THROW( eval_value.operator()<long long>(p, c, "a := b + d"), teascript::exception::unknown_identifier );
// or like that
BOOST_CHECK_EQUAL( eval_value.operator()< bool >(p, c, "undef variable"), true );
// and so on ...
That were my TOP 5 of the most astounding C++20 syntax constructs.
You can try all 5 at godbolt: https://godbolt.org/z/1TEnzx8x1
Do you know other astounding constructs?
Which possible and valid (and maybe useful as well) C++20 syntax construct is your personal No. 1 ?
I want end my Christmas Special with a little and funny excursion.
This is valid syntax as well.
{({()}),({()})}
Well, it is not valid C++, but actually it is valid (but pretty useless, I guess) TeaScript syntax.
Do you know what it yields?
With this final task I will let you enjoy the Christmas time now.
Maybe during your holidays, you wanna like to experiment a little with TeaScript and especially its already available Functional Programming capabilities. No installation required β just download and use it.
I highly welcome every feedback. I hope you enjoyed with this blog post.
I wish you a Merry Christmas ππ πand a Happy New Year 2023 !!! ππππππ
4 replies on “TOP 5 most astounding C++20 Syntax”
Good information
Thank you very much for your feedback! π
Most of them have nothing to do with C++20. Only number 2 does.
Thank you for your comment! π
First of all for clarification: Every number is valid and legal C++20 code. That is, why I chosen it. For example, the throw specifier was removed in C++20. Thus, an example using throw() cannot be taken into account.
Second: Number 1 example is also only possible with at least C++20
https://en.cppreference.com/w/cpp/language/lambda
Happy coding! π