w1th0utnam3 w1th0utnam3 - 2 months ago 14
C++ Question

Alias template for reference type passed as template template argument in SFINAE context

I encountered the following problem with G++ 6.1.0 (

-std=c++14
switch) and I don't understand why the compiler rejects the code.

I have a helper struct
is_well_formed
which checks if a supplied template template argument is well formed when substituting another supplied type into it:

template<template<typename> typename R, typename T, typename = void>
struct is_well_formed : std::false_type {};

template<template<typename> typename R, typename T>
struct is_well_formed<R, T, void_t<R<T>>> : std::true_type {};


I want to check whether a type is referenceable. So my idea was to write the following:

// (#1)
template<class T>
using reference_t = T&;

static_assert(!is_well_formed<reference_t, void>::value, "Reference to void!?");


But I get a compiler error:

main.cpp: In instantiation of 'struct is_well_formed<reference_t, double>':
main.cpp:62:51: required from here
main.cpp:54:20: error: type/value mismatch at argument 1 in template parameter list for 'template<template<class> class R, class T, class> struct is_well_formed'
: std::false_type {};
^
main.cpp:54:20: note: expected a class template, got 'reference_t'
main.cpp:54:20: error: type/value mismatch at argument 1 in template parameter list for 'is_well_formed<R, T, <template-parameter-1-3> >::is_well_formed'
main.cpp:54:20: note: expected a class template, got 'reference_t'


Alternatively the following works with the same
static_assert
:


// (#2)
template<class T>
using reference_t = void_t<T&>;


Furthermore the following works, which really puzzles me:


// (#3)
template<class T>
using pointer_t = T*;

static_assert(is_well_formed<pointer_t, void>::value, "No pointer to void!?");


What is the difference between the three aliases? Is the
void_t<T&>
solution the most elegant? Or is it possible to modify the
is_well_formed
helper struct to support the first
reference
version?

EDIT: I tested the code with MSVC"15" Preview 4, and
(#1)
and
(#3)
work including the asserts. But when I try
(#2)
the assert for the void reference does not work, i.e. information gets lost during substitution and the
false_type
overload is never selected. Which compiler is right?

The
is_well_formed
helper corresponds to the
can_apply
struct from the Stack Overflow documentation page on SFINAE, I just removed the parameter packs. Full example code:

#include <utility>

// Only defined in std for C++17
template <class...>
using void_t = void;

// (#1) Compiler error during substitution in is_well_formed
template<class T>
using reference_t = T&;

// (#2) Ok, asserts work
/*
template<class T>
using reference_t = void_t<T&>;
*/

// (#3) Ok, asserts work
template<class T>
using pointer_t = T*;

template<template<typename> typename R, typename T, typename = void>
struct is_well_formed
: std::false_type {};

template<template<typename> typename R, typename T>
struct is_well_formed<R, T, void_t<R<T>>>
: std::true_type {};

int main(int, char**)
{
static_assert(is_well_formed<reference_t, double>::value, "No reference to double!?");
static_assert(!is_well_formed<reference_t, void>::value, "Reference to void!?");

static_assert(is_well_formed<pointer_t, double>::value, "No pointer to double!?");
static_assert(is_well_formed<pointer_t, void>::value, "No pointer to void!?");

return 0;
}

Answer

It may be a compiler bug and user T.C. reported it on the GCC Bugzilla here after seeing this post. User Jarod42 proposed

template<class T>
using reference = decltype(std::declval<T&>());

as a working alternative for both MSVC15 and GCC 6.1.0. Furthermore he noted that MSVC still requires the long void_t definition

template <class...>
struct make_void { using type = void; };

template <typename... T>
using void_t = typename make_void<T...>::type;

instead of the obvious

template <class...>
using void_t = void;

which prevented option (#2) in the original post from working.