Implicitly convert number to integral const
Asked Answered
G

2

4

Suppose I have the function:

template<size_t N>
void foo(std::integral_constant<size_t,N>);

Right now to use it I do this:

constexpr size_t myNum = 12;
foo(std::integral_constant<size_t,myNum>());

But I would like a way to use it like this:

constexpr size_t myNum = 12;
foo(myNum);

Is there any way to implicitly convert a number to the corresponding std::integral_constant?

Gan answered 14/5, 2016 at 20:43 Comment(11)
For literals, you can have a _c or similar user-defined literal. However, that won't work for a non-literal.Slumber
@Slumber How would that work?Gan
There's lots of info on the feature. Boost.Hana has one, too. Using it would be a simple case of foo(12_c).Slumber
Not sure why you would do that. The whole point is that std::integral_constant are a type. They are not supposed to be objects. So how do you pass a type to a function (templates). But why would you do that when you can use integer as template parameter.Devereux
@LokiAstari, I'd say Hana has made a good case for turning types into objects. tuple[5_c] = "foo"; and filter(tuple, [](auto type) { return is_pointer(type); }) are two examples.Slumber
@chris: Who is Hana. And I have no idea what you are trying to explain or why that is better than (I have no idea what it is replacing).Devereux
@Slumber You should post an answer....that is awesome.....Gan
Just found this by searching google boostorg.github.io/hana/index.html#tutorial-integralGan
@DarthRubik, I don't feel it's comprehensive enough. It covers one specific scenario and says nothing about the rest.Slumber
@LokiAstari, As Darth found (and as I had expanded above), I meant Boost.Hana, which is officially in 1.61.0. It gives a pretty good advantage in the opening of Darth's link: Furthermore, we can now perform compile-time arithmetic using the same syntax as that of normal C++. I suppose it was Louis Dionne, the author, making the case, though.Slumber
@LokiAstari Wanting to call an operator<< or operator>> is a case where an explicit integer template parameter for the number of bits to shift is unavailable, but the compiler can infer that template parameter if the operand is std::integral_constant. This can be useful because shifts with immediate operands can be more efficient then run-time variable operands. e.g. int x = a << 3_cAusterlitz
L
1

I'm afraid that true implicit conversion is not possible. However, you can use macros and compile-time constant detection (see https://mcmap.net/q/246397/-is-is_constexpr-possible-in-c-11) to emulate the desired syntax along with "constexpr overloading".

Here is my C++14 implementation of the trick:

#include <iostream>

// Compile-time constant detection for C++11 and newer
template <typename T> 
constexpr typename std::remove_reference<T>::type makeprval(T && t) 
{
    return t;
}

#define is_const(X) noexcept(makeprval(X)) // broken in Clang
//#define is_const(X) __builtin_constant_p(X) // non-standard but works in GCC and Clang

template <bool c>
struct const_var_impl {
    template <typename CFn, typename VFn>
    static inline auto resolve_branch(CFn cf, VFn vf) {}
};

template <>
struct const_var_impl<true> {
    template <typename CFn, typename VFn>
    static inline auto resolve_branch(CFn cf, VFn vf) {
        return cf();
    }
};

template <>
struct const_var_impl<false> {
    template <typename CFn, typename VFn>
    static inline auto resolve_branch(CFn cf, VFn vf) {
        return vf();
    }
};

#define const_var_branch(X, F) \
    const_var_impl<is_const(X)>::resolve_branch( \
        [&]() { \
            constexpr auto _x_val = is_const(X) ? X : 0; \
            return F(std::integral_constant<decltype(X), _x_val>{}); \
        }, \
        [&]() { \
            return F(X); \
        } \
    )

template <typename T, T c>
void fn_impl(std::integral_constant<T, c> c_arg) {
    std::cout << "Constant " << c_arg << std::endl;
}

template <typename T>
void fn_impl(T v_arg) {
    std::cout << "Variable " << v_arg << std::endl;
}

#define fn(X) const_var_branch(X, fn_impl)

int main(void) {
    int n = 2;

    fn(1); // Prints "Constant 1"
    fn(n); // Prints "Variable 2"
    return 0;
}

You have to use a macro because only a constant literal or a constexpr expression is treated as compile-time constant. Constant propagation cannot be detected as far as I know.

So, we have two overloads of fn_impl. The compile-time and the runtime implementation.

The main idea is to use two lambda functions, one of which will be called depending on the value of is_const(X). Each lambda calls one of the two overloads after explicitly converting our constant/variable X to the correct type. The correct lambda is going to be called via a template specialization of const_var_impl.

The tricky part was to make this work without compiler errors. You somehow have to take the X from the const_var_branch macro and try to create integral_constant from it which is not possible if X is non-constant.

Luckily, we can do this:

constexpr auto _x_val = is_const(X) ? X : 0;

Now the code is always valid. If X is constant, we get its value and instantiate integral_constant with it. Otherwise we end up with zero which is fine, because the lambda with compile-time implementation is not going to be called anyway.

The obvious limitation of this approach is that for each function we want to overload, we have to create a macro and use that for the calls. This is of course not practical if we wanted to call methods or functions within namespaces.

To solve this issue, we could create a similar macro that wraps the function argument only:

#define var_or_const(X) const_var_branch(X, [](auto && x) {return x;})

This macro returns either the X of some integral type T or an instance of std::integral_constant<T, X>.

Then we would use it on fn_impl directly:

fn_impl(var_or_const(n));

This is however still far from implicit.

Liripipe answered 7/10, 2016 at 16:58 Comment(2)
I'm almost certain that your noexcept trick to detect constexpr is broken on clang; did you test with clang? I asked a similar question about this and found that clang has had this bug (not marking constexpr expressions noexcept) for 3 years, as of maybe 6 months ago. Not sure if it's been fixed more recently.Overview
You're right. It is broken. However, I just found out that the non-standard __builtin_constant_p (which is commented out in the code) works in gcc and clang too. I'm gonna edit the answer.Liripipe
A
-1

It's not exactly the syntax you wanted, but:

#include <memory>
using namespace std;

template<int T>
void foo(){} // you can make an integral_constant using T
int main(){
    constexpr int five=5;
  foo<five>();
}

https://godbolt.org/g/lt0VKD

std::integral_type has no constructor - so everything has to be known at compile time and different input parameters (not used for type deduction) don't cause code generation.

Anthelion answered 14/5, 2016 at 21:5 Comment(5)
Actually it has a default constructorGan
You're correct, I meant none that takes a value.. because there's only one value it could possibly take which means it's not actually neededAnthelion
@DarthRubik: But why would you need the object?Devereux
@xaxxon: The comment was at Darth's original comment. Sorry.Devereux
@LokiAstari Lets just say being obsessed with constexpr and template meta programming creates problems like thisGan

© 2022 - 2024 — McMap. All rights reserved.