CppCon 2016: Vittorio Romeo “Implementing `static` control flow in C++14"

By: CppCon

60   13   6196

Uploaded on 09/30/2016

http://CppCon.org

Presentation Slides, PDFs, Source Code and other presenter materials are available at: https://github.com/cppcon/cppcon2016

There has always been great interest in imperative compile-time control flow: as an example, consider all the existing `static_if` proposals and the recently accepted `constexpr_if` construct for C++17.

What if you were told that it is actually possible to implement imperative control flow in C++14?

In this tutorial, the implementation and design of a compile-time `static_if` branching construct and of a compile-time `static_for` iteration construct will be shown and analyzed. These constructs will then be compared to traditional solutions and upcoming C++17 features, examining advantages and drawbacks.

Vittorio Romeo
Bloomberg LP
Software Engineer
London, UK
Vittorio Romeo is an Italian 21 year old Computer Science student at "Università degli Studi di Messina". He began programming at a very young age and is now a C++ enthusiast. While following the evolution of the C++ standard and embracing the newest features, he worked on several open-source projects, including modern general-purpose libraries and free cross-platform indie games. Vittorio is an active member of the C++ community: he participated as a speaker at CppCon 2014/2015, ++it Florence 2015 and at his local Linux Day 2013/2014 events, as a Student/Volunteer at C++Now 2015, and as part of Meeting C++ 2015's student program. He currently maintains a YouTube channel featuring well-received modern C++11 and C++14 tutorials. When he's not writing code, Vittorio enjoys weightlifting and fitness-related activities, competitive/challenging computer gaming and good scifi movies/TV-series.

Videos Filmed & Edited by Bash Films: http://www.BashFilms.com

Comments (9):

By anonymous    2017-09-20

The problem is that the if statement here...

if (std::is_base_of<std::string, T>::value)

...is a run-time branch. Even though is_base_of can be evaluated at compile-time, the compiler is forced to compile both branches of the if statement, even if their correctness relies on the is_base_of condition.


Can I repair it without two different methods?

C++17 introduces if constexpr (...), which does the branching at compile-time. This still requires both branches to be parseable, but only instantiates the one that matches the predicate. Therefore the non-taken branch can be "invalid" and your program will work as expected.

if constexpr (std::is_base_of<std::string, T>::value)
    state = StateSystem::getNumberOfState(object);  
else state = object;

If you do not have access to C++14 and you really don't want to use two different functions, you can implement an equivalent construct to if constexpr(...). The implementation requires a significant amount of boilerplate. The final result will look like this:

static_if(std::is_base_of<std::string, T>{})
    .then([&](auto){ state = StateSystem::getNumberOfState(object); })
    .else_([&](auto){ state = object; })(_);

I gave a talk at CppCon 2016 and Meeting C++ 2016 called "Implementing static control flow in C++14" which explains how static_if works and how to implement it yourself.


If you decide that using two different functions is acceptable, here's how you can solve the issue:

if (!StateSystem::isStateExist(object))
{
    // ...as before...
}
else
{
    dispatch(state, std::is_base_of<std::string, T>{});
    // ...as before...
}

Where dispatch is defined as:

void dispatch(State& state, Object& object, std::true_type)
{
    state = StateSystem::getNumberOfState(object);
}

void dispatch(State& state, Object& object, std::false_type)
{
    state = object;
}

Original Thread

By anonymous    2017-09-20

In C++17, you can achieve exactly what you want with if constexpr(...):

template <typename T> MyClass::doSomething() {
    //this is what I want:
    std::string value = "17";
    if constexpr(std::is_same_v<T, int>) {
         int myInt = atoi(value.c_str());
    } else constexpr(std::is_same_v<T, std::string>) {
         std::string myString = value;
    }
}

In C++14, you can implement your own static_if quite easily. I gave a tutorial talk about this at CppCon 2016 and Meeting C++ 2016.

template <typename T> MyClass::doSomething() {
    //this is what I want:
    std::string value = "17";
    static_if(std::is_same_v<T, int>)
        .then([&](auto) {
             int myInt = atoi(value.c_str());
        })
        .else_if(std::is_same_v<T, std::string>) 
        .then([&](auto) {
             std::string myString = value;
        })();
}

In C++11, you probably want to use function overloading or template specialization. Example with the former (updated with suggestions from Jarod42 in the comments):

template <typename> struct tag { };

void call_dispatch(...) { } // lowest priority

void call_dispatch(tag<std::string>) 
{ 
    std::string myString = value; 
}

void call_dispatch(tag<int>) 
{ 
    int myInt = atoi(value.c_str()); 
}

Usage:

template <typename T> MyClass::doSomething() {
    //this is what I want:
    std::string value = "17";
    call_dispatch(tag<T>{});
}

Original Thread

By anonymous    2017-09-20

In your particular case you could just use uniform initialization, as VTT said:

val = T{};

Also, the Standard Library provides std::is_pointer.


As an answer to the more general question "how do I branch at compile-time?":

  • In C++17, all you have to do is change your if(...) to if constexpr(...):

    template<class T>
    void initialize(T &val) {
        if constexpr(is_pointer(val))
            val = nullptr;
        else
            val = T();
    }
    
  • In C++14, you can implement your own static_if.

  • In C++03/11, you could use tag dispatching:

    template <typename T>
    void initialize_impl(std::true_type /* pointer */, T& val)
    {
        val = NULL;
    }
    
    template <typename T>
    void initialize_impl(std::false_type /* non-pointer */, T& val)
    {
        val = T();
    }
    
    template<class T>
    void initialize(T &val) { initialize_impl(std::is_pointer<T>{}, val); }
    

Original Thread

By anonymous    2017-11-06

If you only need to change s's initialization, you can use overloading:

std::string init(std::string& t)
{
    return t;
}

template <typename T>
std::string init(T& t)
{
    return t.SerializeToString();
}

template <typename T>    
std::string method3(T &t) {
    // ...
    std::string s = init(t);
    // ...
    return s;
}

In C++17, you can use if constexpr:

std::string method3(T &t) 
{
    if constexpr(std::is_same_v<T, std::string>)
    {
        std::string s = t;
        // ...
        return s;
    }
    else
    {
        std::string s = t.SerializeToString();
        // ...
        return s;
    }
}

In C++14, you can use static_if:

std::string method3(T &t) 
{
    static_if(std::is_same<T, std::string>{})
    .then([](auto& x)
    {
        std::string s = x;
        // ...
        return x;
    })
    .else_([](auto& x)
    {
        std::string s = x.SerializeToString();
        // ...
        return x;
    })(t);
}

Original Thread

By anonymous    2017-12-18

In C++17, you can use if constexpr:

template <typename X>
struct Printer
{
    void print_g(const std::string& s)
    {
        if constexpr(X::cp == Type::lowercase)
        {
            std::cout << "lowercase " << std::nouppercase << s << std::endl;
        }
        else if constexpr(X::cp == Type::uppercase)
        {
            std::cout << "uppercase " << std::uppercase << s << std::endl;
        }
        else
        {
            std::cout << "just s: " << s << std::endl;
        }
    }
};

If you do not have access to C++17, consider these options:

  • Use a regular if...else statement. There's no code that needs to be conditionally compiled in your example.

  • Implement static_if in C++14. Here's a talk I gave that explains how to do it: Implementing static control flow in C++14

Original Thread

By anonymous    2018-01-29

and one should be constructible from the other

  • std::pair<T, U> does not define any implicit constructor from std::tuple<T, U>.

  • Similarly, std::tuple<T, U> does not define any implicit conversion operator to std::pair<T, U>.

I think Clang (libc++) is incorrect here, and that GCC and MSVC are correct.


Seems ugly, but doable

It's not too bad:

template <typename... Ts> 
auto make_thing(Ts&&... xs)
{
    if constexpr(sizeof...(xs) == 2)) 
    { 
        return std::make_pair(std::forward<Ts>(xs)...);
    }
    else
    {
        return std::make_tuple(std::forward<Ts>(xs)...);
    } 
}

In C++14, you can replace if constexpr with specialization/enable_if or static_if.

Original Thread

Popular Videos 328

Google Developers

Submit Your Video

If you have some great dev videos to share, please fill out this form.