Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A constexpr function with delayed initialization of local variables

Tags:

I am trying to write a constexpr function of the form:

constexpr int foo(bool cond) {
    int a, b, c;
    if (cond) {
        a = 1;
        b = 2;
        c = 3;
    }
    else {
        a = -1;
        b = -2;
        c = -3;
    }

    return a + b + c;
}

However, the compiler complains that I am using uninitialized variables, despite the fact that the eventual initialization of the local variables is guaranteed.

I could re-write the function to use ternary operators, that is, int a = cond ? 1 : -1;, etc., but I would prefer not to. Is there a way to convince the compiler that the local variables will be initialized?

like image 960
Alessandro Power Avatar asked Jan 06 '17 15:01

Alessandro Power


3 Answers

However, the compiler complains that I am using an uninitialized variables, despite the fact that the eventual initialization of the local variables is guaranteed.

Initialize, or initialize not, there is no "eventual initialization." And, for constexpr functions, there is a requirement that, in [dcl.constexpr]:

The definition of a constexpr function shall satisfy the following requirements: [...] its function-body shall be = delete, = default, or a compound-statement that does not contain [...] a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed.

Can't have uninitialized variables in constexpr functions, which is what a, b, and c are for you.

So what can you do? You could just zero-initialize a, b, and c. That gets around this requirement. Or you could initialize a, b, and c inside of each scope in the if. Or you could defer to another constexpr function to do the summing:

constexpr int f(int a, int b, int c) { return a+b+c; };

constexpr int foo(bool cond) {    
    if (cond) {
        return f(1,2,3);
    }
    else {
        return f(-1,-2,-3);
    }    
}

There are lots of ways around this.

like image 129
Barry Avatar answered Oct 04 '22 22:10

Barry


However, the compiler complains that I am using an uninitialized variables, despite the fact that the eventual initialization of the local variables is guaranteed.

The standard requires all local variables in a constexpr function to be initialized.

From §7.1.5, par. 3 ([dcl.constexpr]):

The definition of a constexpr function shall satisfy the following requirements: [...]

its function-body shall be = delete, = default, or a compound-statement that does not contain [...]

a definition of a variable of non-literal type or of static or thread storage duration or for which no initialization is performed. [...]

constexpr int uninit() {
    int a;     // error: variable is uninitialized
    return a;
}

In C++17, you can use std::tuple, structured bindings, and IIFE (immediately-invoked function expression) to preserve your original structure:

constexpr int foo(bool cond) 
{
    const auto [a, b, c] = [&cond]
    {
        if (cond) 
        {
            return std::tuple(1, 2, 3);
        }
        else
        {
            return std::tuple(-1, -2, -3);
        }    
    }();

    return a + b + c;
}

Since your condition and branching is trivial, a ternary operator will suffice. The code snippet above may help you if in the future your initialization logic becomes more complex, but the one below should be good enough:

constexpr int foo(bool cond) 
{
    const auto [a, b, c] = cond ? std::tuple(1, 2, 3)
                                : std::tuple(-1, -2, -3);

    return a + b + c;
}

In C++14, you can use std::make_tuple and std::get instead:

constexpr int foo(bool cond) 
{
    const auto abc = cond ? std::make_tuple(1, 2, 3) 
                          : std::make_tuple(-1, -2, -3);

    return std::get<0>(abc) + std::get<1>(abc) + std::get<2>(abc);
}

In C++11 you can split the function in two smaller ones:

template <typename TTuple>
constexpr int sum3(const TTuple& abc)
{
    return std::get<0>(abc) + std::get<1>(abc) + std::get<2>(abc);
}

constexpr int foo(bool cond) 
{
    return cond ? sum3(std::make_tuple(1, 2, 3)) 
                : sum3(std::make_tuple(-1, -2, -3));   
}

Barry's solution is definitely better if you decide to go down that route, though.


All the above solutions:

  • Make your a, b, c variables const, which is always a good thing.

  • Only perform a single check on cond, in order to closely resemble the structure of the code in the OP.

like image 34
Vittorio Romeo Avatar answered Oct 04 '22 22:10

Vittorio Romeo


@Borgleader way is enough:

constexpr int foo(bool cond) {
    int a=0, b=0, c=0;
    if (cond) {
        a = 1;
        b = 2;
        c = 3;
    }
    else {
        a = -1;
        b = -2;
        c = -3;
    }

    return a + b + c;
}

compiles without error in C++11 and only warnings that variable declaration in a constexpr function is a C++14 extension and with no warning in C++14 mode (with CLang 3.4.1)

This is clean, simple to read and write and close to original code. But undoubtly, @Barry's solution is nicer.

like image 38
Serge Ballesta Avatar answered Oct 04 '22 22:10

Serge Ballesta