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?

DarthRubik
  • 3,927
  • 1
  • 18
  • 54
  • 1
    For literals, you can have a `_c` or similar user-defined literal. However, that won't work for a non-literal. – chris May 14 '16 at 20:58
  • @chris How would that work? – DarthRubik May 14 '16 at 21:01
  • There's lots of [info](http://en.cppreference.com/w/cpp/language/user_literal) on the feature. Boost.Hana has one, too. Using it would be a simple case of `foo(12_c)`. – chris May 14 '16 at 21:04
  • 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. – Martin York May 14 '16 at 21:05
  • @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. – chris May 14 '16 at 21:09
  • @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). – Martin York May 14 '16 at 21:13
  • @chris You should post an answer....that is awesome..... – DarthRubik May 14 '16 at 21:15
  • Just found this by searching google https://boostorg.github.io/hana/index.html#tutorial-integral – DarthRubik May 14 '16 at 21:16
  • @DarthRubik, I don't feel it's comprehensive enough. It covers one specific scenario and says nothing about the rest. – chris May 14 '16 at 21:30
  • @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. – chris May 14 '16 at 21:34
  • @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_c – mabraham Dec 11 '17 at 01:39

2 Answers2

1

I'm afraid that true implicit conversion is not possible. However, you can use macros and compile-time constant detection (see https://stackoverflow.com/a/13305072/6846474) 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.

Community
  • 1
  • 1
  • 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. – Nir Friedman Oct 07 '16 at 18:17
  • 1
    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. –  Oct 07 '16 at 22:19
-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.

xaxxon
  • 19,189
  • 5
  • 50
  • 80