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

2

10

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.

Burin answered 12/7, 2023 at 17:9 Comment(11)
Something like constexpr and std::enable_if comes to mind.Posthorse
@πάνταῥεῖ Yes, I have managed to implement some what limited version with std::conditional but seeking better solution from betters :)Burin
Would be nice to see a minimal reproducible example containing your solution, and if you have a working solution already, it's probably better to ask at codereview.stackexchange.comPosthorse
C++11 can't do this. If you can update to C++20 you can use std::is_constant_evaluatedMephitic
@NathanOliver-IsonStrike It checks if the return value is used in a constexpr context, not if the arguments are constexpr.Pase
@Pase std::is_constant_evaluated returns true if it is called in a constexpr context. They show that on the cppreference in thier constexpr double power(double b, int x) example codeMephitic
@NathanOliver-IsonStrike My point is, if I do auto x = foo(42);, I want the "compile-time value" branch to be taken, but std::is_constant_evaluated (and if consteval) return false here. Being forced to make the variable constexpr severely limits the usefulness of what OP wants to do. One needs a compiler extension for this, such as __builtin_constant_p.Pase
@NathanOliver-IsonStrike Unfortunately because this is potentially for embedded project, C++ 20 is not available :(Burin
Do you want specifically a branch or would a function overload suffice? The later should be considerably easier.Comedic
@EugeneRyabtsev I want whatever kind of compile time branching including std::enable_if or specialization, overloading or even considerably certain optimization to keep api of foo uniform.Burin
If you already have bar objects, then why not simply skip the step of getting the length at all? You could have separate foo overloads accepting arrays or vectors to achieve the same even easier: foo(bar);. Sure, only covers this specific part, not catches up with any other function returning a size, but it's a starting point at least...Effervescent
J
9

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.

Jiffy answered 12/7, 2023 at 19:16 Comment(6)
Thank you for the suggestion. But I don't really get the point. You put the () at the end of returning clause. Isn't it just return regular size_t?Burin
It works fine in GCC and MSVC, but not in Clang, where foo() creates vector instead of array: gcc.godbolt.org/z/shh7Whn4fTrifle
@HenriqueBucher It somewhat does, since std::integral_constant could be used with runtime value in some context. I did get some result with std::integral_constant with C++ 11. Will update the question after a bit more test,,,Burin
@Burin the () creates a std::integral_constant object by value-initialization; an extra pair of parentheses ()() would call it to return a size_t. You could also use {} - this is mainly a matter of style.Jiffy
@Trifle interesting, I wonder if this is related to open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2280r4.html? There's a fairly simple workaround which is to use decltype(length)::value in the concept check: gcc.godbolt.org/z/6caMh5od8Jiffy
The issue is resolved for Clang 19: github.com/llvm/llvm-project/issues/63845Trifle
S
2

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".

Stotts answered 12/7, 2023 at 17:57 Comment(2)
MSVC doesn't have __builtin_constant_p.Pase
Does "most mainstream C++ compilers" mean GNU-compatible compilers?..Woodsum

© 2022 - 2024 — McMap. All rights reserved.