I want to return void
if template parameter is void
, and return T&
if template parameter is T
. So, I write the next MRE code:
#include<type_traits>
template<class T>
struct Foo {
using X = std::conditional<std::is_void_v<T>, void, T&>::type;
X bar(){ }
};
int main(){
Foo<void> foo;
return 0;
}
I compile this code with g++-13 --std=c++20 main.cpp
or clang++ --std=c++20 main.cpp
.
I am getting this error:
main.cpp:5:58: error: cannot form a reference to 'void'
5 | using X = std::conditional<std::is_void_v<T>, void, T&>::type;
| ^
main.cpp:11:15: note: in instantiation of template class 'Foo<void>' requested here
11 | Foo<void> foo1;
| ^
1 error generated.
Why? And how to use std::conditional
with T&
?
The problem is there is no "template indirection" preventing the compiler from trying to fill in the T
in T&
and the compiler needs to instantiate all template arguments for void
immediately.
You can add a level of indirection by using std::add_lvalue_reference_t
: https://godbolt.org/z/cesc9sWT4
using X = std::conditional_t<std::is_void_v<T>, void, std::add_lvalue_reference_t<T>>;
You can also work around this by adding that level of instantiation delay with an explicit helper template which is specialized on void
: https://godbolt.org/z/6vxjYG6ez
template<typename T>
struct ReferenceOrVoid
{
using type = T&;
};
template<>
struct ReferenceOrVoid<void>
{
using type = void;
};
template<typename T>
using ReferenceOrVoid_t = typename ReferenceOrVoid<T>::type;
using X = ReferenceOrVoid_t<T>;
Unfortunately, that means not using std::conditional_t
directly and is a visual indirection on what's going on. On the other hand, it names the operation you're doing, so you don't have to parse the type trait expression to know what it's doing.