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.
_c
or similar user-defined literal. However, that won't work for a non-literal. – Slumberfoo(12_c)
. – Slumberstd::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. – Devereuxtuple[5_c] = "foo";
andfilter(tuple, [](auto type) { return is_pointer(type); })
are two examples. – Slumber