Sure, it's possible. However, before I explain the solution, please allow me to suggest that you have made the problem unnecessarily difficult by insisting on a particular interface which is not (in my opinion) actually cleaner than the alternatives. Essentially, you are asking for fun<arg>
to either take arg
by value or by reference, depending on which one is actually well-formed. In the case of a1
, it may only be taken by value; it cannot be taken by reference because it doesn't have static storage duration. In the case of a2
, it cannot be taken by value because it wasn't declared constexpr
, but can be taken by reference because it has static storage duration.
The code using your proposed version of fun
is difficult to read, because the reader, seeing fun<arg>
, does not immediately know whether arg
is being taken by value or by reference. The reader must infer which one it is, based on the reader's own knowledge of whether arg
is a permitted non-type template parameter by value or by reference. Furthermore, some arguments may qualify as either, and in that case the reader would also have to know which default the implementer of fun
has selected for that case, in order to know what is going on.
Again, this is my opinion only: it would be much simpler if you wrote separate functions, perhaps calling them fun_val
and fun_ref
, where fun_val<a1>()
and fun_ref<a2>()
are well-formed. For this, we should define two wrapper classes, one which takes the argument by value, and one by reference:
template <typename T>
struct wrap_value {
using value_type = T;
T thing;
constexpr wrap_value(T thing) : thing(thing) {}
};
template <typename T>
wrap_value(const T&) -> wrap_value<T>;
template <typename T>
struct wrap_reference {
using value_type = T;
const T& thing;
constexpr wrap_reference(const T& thing) : thing(thing) {}
};
template <typename T>
wrap_reference(const T&) -> wrap_reference<T>;
template <wrap_value V>
void fun_val() {
std::cout << "value\n";
}
template <wrap_reference V>
void fun_ref() {
std::cout << "reference\n";
}
struct X {
int a;
};
int main() {
constexpr auto a1 = &X::a;
static const auto a2 = &X::a;
static const int x = 42;
fun_val<a1>(); // OK
fun_ref<a1>(); // Error
fun_val<a2>(); // Error
fun_ref<a2>(); // OK
fun_val<x>(); // OK; uses value of x
fun_ref<x>(); // OK; uses address of x
}
Now, if you insist on having a single name fun
, then the key is to recognize that a1
and a2
have the same type, so a single application of CTAD will never be able to figure out the correct wrapper type to make the invocation well-formed. Instead, you have to use SFINAE with two overloads: the one that is invalid for the given template argument (because it takes the argument by value (resp. reference) that cannot be taken by value (resp. reference)) is discarded. Basically, rename both fun_val
and fun_ref
in the above example to simply fun
:
template <wrap_value V>
void fun() {
std::cout << "value\n";
}
template <wrap_reference V>
void fun() {
std::cout << "reference\n";
}
This works fine in the case of a1
and a2
, for which only one of the two overloads is a candidate. But in the case of x
, it will be ambiguous. Let's say you want to force the by-value overload to be selected in that case. We can do that by inserting a constraint that makes the by-reference overload not a candidate:
template <wrap_reference V> requires(!requires { fun<wrap_value<typename decltype(V)::value_type>(V.thing)>(); })
void fun() {
std::cout << "reference\n";
}
You can view the full working example here.