Various Template Improvements with C++20

Contents[Show]

Admittedly, I present in this post a few small improvements to templates and to C++20 in general. Although these improvements may seem not so impressive to you, they make C++20 more consistent and, therefore, less error-prone when you program generic.

 TimelineCpp20CoreLanguage

Today's post is about conditionally explicit constructors and new non-type template parameters.

Conditionally Explicit Constructors

Sometimes, you want to have a class which should have constructors accepting various different types. For example, you have a class VariantWrapper which holds a std::variant accepting various different types.

class VariantWrapper {

    std::variant<bool, char, int, double, float, std::string> myVariant;

};

 

To initialize the myVariant with bool, char, int, double, float, or std::string, the class VariantWrapper needs constructors for each listed type. Laziness is a virtue - at least for programmer - , therefore, you decide to make the constructor generic. 

The class Implicit exemplifies a generic constructor.

// explicitBool.cpp

#include <iostream>
#include <string>
#include <type_traits>

struct Implicit {
    template <typename T>        // (1)
    Implicit(T t) {
        std::cout << t << std::endl;
    }
};

struct Explicit {
    template <typename T>
    explicit Explicit(T t) {    // (2)
        std::cout << t << std::endl;
    }
};

int main() {
    
    std::cout << std::endl;
    
    Implicit imp1 = "implicit";
    Implicit imp2("explicit");
    Implicit imp3 = 1998;
    Implicit imp4(1998);
    
    std::cout << std::endl;
    
    // Explicit exp1 = "implicit";  // (3)
    Explicit exp2{"explicit"};      // (4)
    // Explicit exp3 = 2011;        // (3)
    Explicit exp4{2011};            // (4)
    
    std::cout << std::endl;  

} 

 

Now, you have an issue. A generic constructor (1) is a catch-all constructor because you can invoke them with any type. The constructor is way too greedy.  By putting an explicit in front of the constructor (2). the constructor becomes explicit. This means that implicit conversions (3) are not valid anymore. Only the explicit calls (4) are valid.

Thanks to Clang 10, here is the output of the program:

explicitBool

This is not the and of the story. Maybe, you have a type MyBool that should only support the implicit conversion from bool, but no other implicit conversion. In this case, explicit can be used conditionally.

// myBool.cpp

#include <iostream>
#include <type_traits>
#include <typeinfo>

struct MyBool {
    template <typename T>
    explicit(!std::is_same<T, bool>::value) MyBool(T t) {  // (1)
        std::cout << typeid(t).name() << std::endl;
    }
};

void needBool(MyBool b){ }                                 // (2)

int main() {

    MyBool myBool1(true);                              
    MyBool myBool2 = false;                                // (3)
    
    needBool(myBool1);
    needBool(true);                                        // (4)
    // needBool(5);
    // needBool("true");
    
}

 

The explicit(!std::is_same<T,  bool>::value) expression guarantees that MyBool can only implicitly created from a bool value. The function std::is_same is a compile-time predicate from the type_traits library. Compile-time predicate means, std::is_same is evaluated at compile-time and returns a boolean. Consequently, the implicit conversion from bool in (3) and (4) is possible but not the commented out conversions from int and a C-string.

You are right when you argue that a conditionally explicit constructor would be possible with SFINAE. But honestly, I don't like the corresponding SFINAE using constructor, because it would take me a few lines to explain it. Additionally, I only get it right after the third try.

template <typename T, std::enable_if_t<std::is_same_v<std::decay_t<T>, bool>, bool> = true>
MyBool(T&& t) {
    std::cout << typeid(t).name() << std::endl;
}

 

I think I should add a few explaining words. std::enable_if is a convenient way to use SFINAE. SFINAE stands for Substitution Failure Is Not An Error and applies during overload resolution of a function template. It means when substituting the template parameter fails, the specialisation is discarded from the overload set but cause no compiler error. This exactly happens in this concrete case. The specialization is discarded if std::is_same_v<std::decay_t<T>, bool> evaluates to false. std::decay<T> applies conversions to T such as removing const, volatile or a reference from T. std::decay_t<T> is a convenient syntax for std::decay<T>::type. The same holds for std::is_same_v<T, bool> which is short for std::is_same<T, bool>::value.

As my German reader pre alpha pointed out: the constructor using SFINAE is way too greedy. It disables all non-bool constructors.

Beside my longish explanation, there is an additional argument that speaks against SFINAE and for a conditionally explicit constructor: performance. Simon Brand pointed out in his post "C++20's Conditionally Explicit Constructors", that explicit(bool) made the template instantiation for Visual Studio 2019 about 15% faster compared to SFINAE.

With C++20, additional non-type template parameters are supported.

 

Rainer D 6 P2 540x540Modernes C++ Mentoring

Stay informed about my mentoring programs.

 

 

Subscribe via E-Mail.

New non-type Template Parameter

With C++20, floating-points and classes with constexpr constructors are supported as non-types.

C++ supports non-types as template parameters. Essentially non-types could be

  • integers and enumerators
  • pointer  or references to objects, functions and to attributes of a class
  • std::nullptr_t

When I ask in the students in my class if they ever used a non-type as template parameter they say: No! Of course, I answer my own tricky question and show an often-used example for non-type template parameters:

std::array<int, 5> myVec;

 

5 is a non-type and used as a template argument. We are just used to it. Since the first C++-standard C++98, there is a discussion in the C++ community to support floating points as a template parameter. Now, we C++20 we have it:

 

// nonTypeTemplateParameter.cpp

struct ClassType {
    constexpr ClassType(int) {}  // (1)
};

template <ClassType cl>          // (2)
auto getClassType() {
    return cl;
}

template <double d>              // (3)
auto getDouble() {
    return d;
}

int main() {

    auto c1 = getClassType<ClassType(2020)>();

    auto d1 = getDouble<5.5>();  // (4)
    auto d2 = getDouble<6.5>();  // (4)

}

 

ClassType has a constexpr constructor (1) and can, therefore, be used as a template argument (2). The same holds for the function template getDouble (3) which accepts only doubles. I want to emphasize is explicit, that each call of the function template getDouble (4) with a new argument triggers the instantiation of a new function getDouble.  This means that there are two instantiations for the doubles 5.5 and 6.5 are created.

If Clang would already support this feature I could show you with C++ Insights that each instantiation for 5.5 and 6.5 creates a fully specialized function template. At least, thanks to GCC, I can show you the relevant assembler instructions with the Compiler Explorer.

nonTypeTemplateParameter

The screenshot shows, that the compiler created for each template argument a function.

What's next?

As templates, lambdas are also improved in various ways in C++20. My next post is about these various improvements.

 

 

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Animus24, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, and Ann Shatoff.

 

Thanks in particular to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, and Slavko Radman.

 

 

My special thanks to Embarcadero CBUIDER STUDIO FINAL ICONS 1024 Small

 

My special thanks to PVS-Studio PVC Logo

 

My special thanks to Tipi.build tipi.build logo

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.

  • C++ - The Core Language
  • C++ - The Standard Library
  • C++ - Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me

Modernes C++,

RainerGrimmDunkelBlauSmall

Comments   

0 #1 FederAndInk 2020-07-29 20:30
What's cool also is that you can do explicit(false) to say explicitly that it is an implicit ctor or cast
Quote

Mentoring

Stay Informed about my Mentoring

 

English 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)

All tags

Blog archive

Source Code

Visitors

Today 336

Yesterday 7888

Week 8224

Month 152395

All 11633549

Currently are 158 guests and no members online

Kubik-Rubik Joomla! Extensions

Latest comments