return type deduction fails whyen moving to C++ 14

Hi,

I downloaded the following code and it seemed to me that we could benefit from moving it to C++ 14. I thought it was a matter of changing the return types to decltype(auto) but when using the templates I get many errors. Here is the original code taken from:

http://stackoverflow.com/questions/87372/check-if-a-class-has-a-member-function-of-a-given-signature/16867422#16867422:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template< typename T, typename E>
struct has_const_reference_op
{
	/* SFINAE operator-has-correct-sig :) */
	template<typename A>
	static std::true_type test(E(A::*)() const) {
		return std::true_type();
	}

	/* SFINAE operator-exists :) */
	template <typename A>
	static decltype(test(&A::operator*))
		test(decltype(&A::operator*), void *) {
		/* Operator exists. What about sig? */
		typedef decltype(test(&A::operator*)) return_type;
		return return_type();
	}

	/* SFINAE game over :( */
	template<typename A>
	static std::false_type test(...) {
		return std::false_type();
	}

	/* This will be either `std::true_type` or `std::false_type` */
	typedef decltype(test<T>(0, 0)) type;

	static const bool value = type::value; /* Which is it? */
};


Changing return types I get:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
template< typename T, typename E>
struct has_const_reference_op
{
	/* SFINAE operator-has-correct-sig :) */
	template<typename A>
	static decltype(auto) test(E(A::*)() const) {
		return std::true_type();
	}

	/* SFINAE operator-exists :) */
	template <typename A>
	static decltype(auto)
		test(decltype(&A::operator*), void *) {
		/* Operator exists. What about sig? */
		typedef decltype(test(&A::operator*)) return_type;
		return return_type();
	}

	/* SFINAE game over :( */
	template<typename A>
	static decltype(auto) test(...) {
		return std::false_type();
	}

	/* This will be either `std::true_type` or `std::false_type` */
	typedef decltype(test<T>(0, 0)) type;

	static const bool value = type::value; /* Which is it? */
};


My only explanation is that the code is too complex to enable return type deduction. I tried using Visual Studio 2015 Update 3, both with default compiler and with Clang 3.7 with Microsoft CodeGen, and both threw a lot of errors...

Thanks for any insight!!

Juan Dent
Last edited on
Hi,

I am very much a newbie when it comes to this template magic stuff, but I couldn't help but have two thoughts:

1. I don't see the auto as an argument to decltype is going to work. Is auto in that context a prvalue, but there is no type T to yield, it's supposed to be the expression of type T, not the template parameter T ? That is, explanation 2c in the documentation? In the reference example, when decltype is used as a return type it's argument is an actual type, option 1 of the syntax? Maybe static auto by itself, might work?

2. If it ain't broke don't try to fix it :+D

http://en.cppreference.com/w/cpp/language/decltype

Hope this helps a little bit :+)

Regards
As far as possible, use stawman functions (declared, but not defined) for metaprogramming.
Ideally, do not special case for user-defined types (has_const_reference_op should also work with raw pointers).
Factoring code into easily digestible, reusable components is a good idea, in every kind of programming.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template< typename T > struct is_const_dereferencible
{
    template< typename U > static
    std::true_type test( const U&& u, typename std::remove_reference< decltype( *u ) >::type* = nullptr ) ;

    static std::false_type test(...) ;

    using type = decltype( test( std::declval< typename std::decay<T>::type >() ) ) ;
    static constexpr bool value = type::value ;
};

template< typename T, typename = void >
struct const_dereferenced_type { struct nothing ; using type = nothing ; using base_type = nothing ; } ;

template< typename T >
struct const_dereferenced_type< T, typename std::enable_if< is_const_dereferencible<T>::value >::type >
{
    using type = decltype( *std::declval< typename std::add_const< typename std::decay<T>::type >::type >() ) ;
    using base_type = typename std::decay<type>::type ;
};

template< typename T, typename E > struct has_const_reference_op :
                    std::conditional< is_const_dereferencible<T>::value &&
                                      std::is_same< typename const_dereferenced_type<T>::type, E >::value,
                                      std::true_type, std::false_type >::type {};

http://coliru.stacked-crooked.com/a/0ba3d293b6e2a51a
http://rextester.com/UMHR9619
Hi JLBorges,

Would you be so kind as to comment on why the replacement of return types in the original code presented here does not work when replaced with 'decltype(auto)'?

My interpretation was that C++ 14 would always be able to deduce the return type and that using decltype(auto) would also do that and preserve the referenceness of the return type.

Did I misinterpret this?

I am truly thankful for your contributions!! I am myself a C++ developer that has been "out of the game" for the last 10 years and am coming back as fast as possible...

Regards,
Juan
> why the replacement of return types in the original code presented here does not work
> when replaced with 'decltype(auto)'?

> My interpretation was that C++ 14 would always be able to deduce the return type
> and that using decltype(auto) would also do that and preserve the referenceness of the return type.

The deduced return type would require that the template is instantiated; therefore, on line 15, the unary overload of has_const_reference_op<T,E>::test must be instantiated. At this point, has_const_reference_op<T,E> is an incomplete type and compilers are unhappy because the nested instantiation of a template (in an unevaluated context) involves an incomplete type. Caveat: AFAIK.

If there is no demand for the nested instantiation of a member of an incomplete type, deduced return type via decltype(auto) would be fine. For instance:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template< typename T > struct is_const_dereferencible
{
    template< typename U > static
    decltype(auto) test( const U&& u, typename std::remove_reference< decltype( *u ) >::type* = nullptr ) { return std::true_type() ; }

    static decltype(auto) test(...) { return std::false_type{} ; }

    using type = decltype( test( std::declval< typename std::decay<T>::type >() ) ) ;
	static constexpr bool value = type::value ;
};

template< typename T, typename = void >
struct const_dereferenced_type { struct nothing ; using type = nothing ; using base_type = nothing ; } ;

template< typename T >
struct const_dereferenced_type< T, typename std::enable_if< is_const_dereferencible<T>::value >::type >
{
    using type = decltype( *std::declval< typename std::add_const< typename std::decay<T>::type >::type >() ) ;
    using base_type = typename std::decay<type>::type ;
};

template< typename T, typename E > struct has_const_reference_op :
                    std::conditional< is_const_dereferencible<T>::value &&
                                      std::is_same< typename const_dereferenced_type<T>::type, E >::value,
                                      std::true_type, std::false_type >::type {};

http://coliru.stacked-crooked.com/a/a3b62f832effe46b


In any case, the original code, even without the deduced return type, is broken.
When a class has an overloaded operator*(), it selects the wrong (non const) overload.
http://coliru.stacked-crooked.com/a/1ecf07efb84fae63
http://rextester.com/NCEW16517
Last edited on
Topic archived. No new replies allowed.