Vincent
Vincent

Reputation: 60341

if / else at compile time in C++?

Consider the following code :

#include <iostream>
#include <type_traits>

template<typename T> class MyClass
{
    public:
        MyClass() : myVar{0} {;}
        void testIf() {
            if (isconst) {
                myVar;
            } else {
                myVar = 3;
            }
        }
        void testTernary() {
            (isconst) ? (myVar) : (myVar = 3);
        }

    protected:
        static const bool isconst = std::is_const<T>::value;
        T myVar;
};

int main()
{
    MyClass<double> x;
    MyClass<const double> y;
    x.testIf();
    x.testTernary();
    y.testIf(); // <- ERROR
    y.testTernary(); // <- ERROR
    return 0;
}

For x (non-const) there is no problem. But y (const data type) cause an error even if the condition in if/else is known at compile-time.

Is there any possibility to not compile the false condition at compile-time ?

Upvotes: 26

Views: 32661

Answers (6)

C++17 if constexpr

Oh yes, it has arrived:

main.cpp

#include <cassert>
#include <type_traits>

template<typename T>
class MyClass {
    public:
        MyClass() : myVar{0} {}
        void modifyIfNotConst() {
            if constexpr(!isconst) {
                myVar = 1;
            }
        }
        T myVar;

    protected:
        static constexpr bool isconst = std::is_const<T>::value;
};

int main() {
    MyClass<double> x;
    MyClass<const double> y;
    x.modifyIfNotConst();
    y.modifyIfNotConst();
    assert(x.myVar == 1);
    assert(y.myVar == 0);
    return 0;
}

GitHub upstream.

Compile and run:

g++-8 -std=c++17 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

See also: Difference between "if constexpr()" Vs "if()"

This will be really cool together with C++20 "string literal template arguments": Passing a string literal as a parameter to a C++ template class

Tested in Ubuntu 16.04, GCC 8.1.0.

Upvotes: 29

ecatmur
ecatmur

Reputation: 157314

The simplest fix is partial template specialization:

template<typename T> class MyClassBase
{
    public:
        MyClassBase() : myVar{0} {;}

    protected:
        T myVar;
};

template<typename T> class MyClass: MyClassBase<T>
{
    public:
        void testIf() { myVar = 3; }
};

template<typename T> class MyClass<const T>: MyClassBase<const T>
{
    public:
        void testIf() { myVar; }
};

Another option is delegation:

template<typename T> class MyClass
{
    public:
        MyClass() : myVar{0} {;}
        void testIf() { testIf_impl(std::integral_constant<bool, isconst>()); }

    protected:
        static const bool isconst = std::is_const<T>::value;
        T myVar;

    private:
        void testIf_impl(std::true_type) { myvar; }
        void testIf_impl(std::false_type) { myVar = 3; }
};

SFINAE is another option, but is generally not preferred for this case:

template<typename T> class MyClass
{
    public:
        MyClass() : myVar{0} {;}
        template
        <typename U = void>
        typename std::enable_if<std::is_const<T>::value, U>::type testIf() { myvar; }
        template
        <typename U = void>
        typename std::enable_if<!std::is_const<T>::value, U>::type testIf() { myvar = 3; }

    protected:
        static const bool isconst = std::is_const<T>::value;
        T myVar;
};

Upvotes: 15

hansmaad
hansmaad

Reputation: 18905

Try this:

template<typename T> 
class MyClass
{
    T myVar;
public:
    MyClass() : myVar(0) {}

    void testIf()
    {
        assign(myVar, 3);
    }
 private:

    template<typename V>
    void assign(V& destination, int value)
    {
        destination = value;
    }
    template<typename V>
    void assign(const V& destination, int value)
    {

    }
};

Upvotes: 0

Bo Persson
Bo Persson

Reputation: 92211

You can specialize the class for const types

template<typename T>
class MyClass 
{
   // Whatever you need to do
};

template<typename T>
class MyClass<const T> 
{
   // Whatever you need to do for const types
};

Upvotes: 5

mfontanini
mfontanini

Reputation: 21900

The class template gets compiled for the given type. Even if the control flow doesn't get to the assignment, that assignment is compiled as well. Since the member is const, the compilation will fail.

You could use some form of SFINAE to skip that assignment, but it's not going to work as it is now.

This works(I've removed the testTernary member function for simplicity):

#include <iostream>
#include <type_traits>

template<typename T> class MyClass
{
    public:
        MyClass() : myVar{0} {;}

        template<class U = T>
        typename std::enable_if<std::is_const<U>::value>::type testIf() {
            myVar;
        }

        template<class U = T>
        typename std::enable_if<!std::is_const<U>::value>::type testIf() {
            myVar = 3;
        }

    protected:
        static const bool isconst = std::is_const<T>::value;
        T myVar;
};

int main()
{
    MyClass<double> x;
    MyClass<const double> y;
    x.testIf();
    y.testIf();
    return 0;
}

Upvotes: 3

Daniel
Daniel

Reputation: 6745

If the else branch weren't compiled, then your function would have a completely different meaning. You can't just not compile part of your code. If you don't want it to execute, don't write it. It's not like the function is compiled separately for each time it is called.

The whole point of a type system is to avoid accidentally trying to do things like assigning to const variables. You would have to write an entirely new (or overloaded) function that doesn't assign to that variable.

Upvotes: 0

Related Questions