lighthouse
lighthouse

Reputation: 553

In C++ 11 Is there any way to branch by if a function parameter is compile-time constant or not?

Is there anyway I can tell function argument is compile time-constant or not in C++ 11?. I would like to branch the functions body based on the results. For example, like below;

template <T>
size_t get_length(const T &t)
{
    return t.size();
}

template <typename T, size_t N>
size_t get_length(const std::array<T, N> &t)
{
    return N;
}

// Maybe some template
??? foo(size_t length)
{
    ...
    // If length could be evaluated at compile time,
    return std::array<int, length>{};

    ...
    // Otherwise,
    return std::vector<int>(length);
}


// ??? and auto should be std::array if bar is std::array, std::vector if bar is std::vector or etc.
auto x = foo(get_length(bar));

I believe this need two task; First it should be able to determine if a value is compile time evaluable or not. Second, convert a value to compile time constant if possible.

Is there anyway one can do this? Or is there any reason this is impossible? It seems some say to use template function, but that forces caller to put compile time constant and not what I am looking for.

Upvotes: 10

Views: 1424

Answers (2)

Henrique Bucher
Henrique Bucher

Reputation: 4474

Not strictly in C++11 as per the language but most mainstream C++ compilers do accept the builtin __builtin_constant_p function to indicate if the value is constant or not.

Example:

#include <iostream>

template < typename T >
inline void print( T x ) {
    if ( __builtin_constant_p(x) ) {
        std::cout << "Constant:" << x << std::endl;
    } else {
        std::cout << "Variable:" << x << std::endl;
    }
}

int main() {
    int x = 1;
    const int a = 1;

    // It's in a variable but it's a constant in the context
    print( x );
    // Constant without a doubt
    print( a );
    // Some compilers will consider the calls below constant 
    // after loop-unrolling them into 1..2..3 constants
    for ( int j=0; j<3; ++j ) {
        print(j);
    }
}

Produces:

Program stdout
Constant:1
Constant:1
Variable:0
Variable:1
Variable:2

Godbolt: https://godbolt.org/z/KaxWd6xcn

Beware though that what some compilers consider a "constant" others will not consider.

Clang 14+ will consider the variables in the loop as constant while gcc and clang <14 will not. GCC 4.6.4 will consider all variables.

So not sure how useful this will be as it is somewhat "subjective".

Upvotes: 2

ecatmur
ecatmur

Reputation: 157404

This is reasonably possible by changing get_length to return an integral_constant:

template <typename T, size_t N>
auto get_length(const std::array<T, N> &t)
{
    return std::integral_constant<std::size_t, N>();
}

Then, to use the result,

auto foo(auto length) {
    // If length could be evaluated at compile time,
    if constexpr (requires { typename std::array<int, length>; })
        return std::array<int, length>{};
    // Otherwise,
    else
        return std::vector<int>(length);
}

This uses facilities from C++14 through C++20 (terse templates, if constexpr, concepts) but it should be straightforward to backport to C++11 if required.

Upvotes: 9

Related Questions