Compiler discrepancy concerning type-deduction - c++

I'm trying to construct an object from a templated class which should be of iterator-type based on the container that is being passed through its constructor. To do so, I use a deduction guide which extracts the iterator member-type.
This seems to work on both Clang and GCC. The problem is that when making some seemingly unrelated changes to this class, the code no longer compiles on Clang (but does on some versions of GCC). Here is the code, which I unfortunately couldn't make much smaller:
#include <array>
#include <iterator>
#include <type_traits>
template<typename It>
using category_t = typename std::iterator_traits<It>::iterator_category;
//
template<typename Iter>
struct is_random_access_iter
: std::is_convertible<
category_t<Iter>,
std::random_access_iterator_tag> {};
template<typename T>
struct has_random_access_iter
: is_random_access_iter<typename T::iterator> {};
template<typename T>
struct is_unary_type
: std::conjunction<
std::is_default_constructible<T>,
std::is_copy_constructible<T>> {};
//
template<typename Iter>
inline constexpr auto is_random_access_iter_v
= bool{is_random_access_iter<Iter>{}};
template<typename T>
inline constexpr auto has_random_access_iter_v
= bool{has_random_access_iter<T>{}};
template<typename T>
inline constexpr auto is_unary_type_v
= bool{is_unary_type<T>{}};
//
template<typename It, int Size,
typename = std::enable_if_t<is_random_access_iter<It>{}>>
struct derp {
// constexpr derp() = default;
template<typename T,
typename = std::enable_if_t<is_unary_type<T>{}>,
typename = std::enable_if_t<has_random_access_iter_v<T>>>
// typename = std::enable_if_t<is_random_access_iter_v<typename T::iterator>>>
constexpr explicit derp(T& herp)
: iter{herp.begin()} {}
It iter;
};
template<typename T>
derp(T) -> derp<typename T::iterator, std::tuple_size<T>{}>;
template<typename T>
struct wrap {
constexpr wrap() = default;
constexpr explicit wrap(T& herp)
: herp{herp} {}
derp<typename T::iterator, std::tuple_size<T>{}> herp;
};
auto main() -> int {
auto arr = std::array<int, 3>{};
wrap{arr};
}
One of the problems comes to light when uncommenting the default constructor of derp. It seems as if derp is being instantiated by its own type, instead of the iterator-type from the passed-in container:
GCC ARM 7.2.1:
<source>:44:9: required by substitution of 'template<class T, class, class> constexpr derp<int*, 3, void>::derp(T&) [with T = const derp<int*, 3, void>; <template-parameter-1-2> = void; <template-parameter-1-3> = <missing>]'
<source>:66:13: required from here
<source>:16:8: error: no type named 'iterator' in 'const struct derp<int*, 3, void>'
Clang:
<source>:17:41: error: no type named 'iterator' in 'derp<int *, 3, void>'
: is_random_access_iter<typename T::iterator> {};
~~~~~~~~~~~~^~~~~~~~
<source>:44:37: note: in instantiation of template class 'has_random_access_iter<const derp<int *, 3, void>>' requested here
typename = std::enable_if_t<has_random_access_iter<T>{}>>
^
<source>:46:24: note: in instantiation of default argument for 'derp<const derp<int *, 3, void>, void>' required here
constexpr explicit derp(T& herp)
The code no longer compiles on Clang and GCC ARM 7.2.1. It does however compile on GCC trunk. Why does enabling the default constructor cause a compilation error? And why does GCC >= 11.1 seems to be unaffected?
Live example

Related

SFINAE type trait with pointer-to-member-function fails

I'm practicing SFINAE and would like to implement a type trait in order to check if a given class T contains a method print(). I have the following two variants:
// (1)
template <typename T, typename = int>
struct has_print_method : std::false_type {};
template <typename T>
struct has_print_method<T, decltype(&T::print, 0)> : std::true_type {};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
// (2)
template <typename T>
struct has_print_method{
template <typename U, typename = void> struct helper : std::false_type{};
template <typename U> struct helper<U, decltype(&U::print)> : std::true_type{};
static const bool value = helper<T, void (T::*)() const>::value;
};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
(1) only checks for the existence of a member method print() and ignores the member method's signature. Whereas (2) checks the method's signature, i.e. it requires a member method void print() const.
I test both variants via:
#include <iostream>
#include <type_traits>
// Simple Class A
struct A{
int a;
public:
void print() const{}
};
// (1) or (2) here
template<typename T, std::enable_if_t<has_print_method_v<T>, bool> = true>
void print(T t) {
t.print();
}
void print(double x){
std::cout << x << '\n';
}
int main() {
A a;
print(a);
print(1.0); // (*)
return 0;
}
Using the type trait (1) and compiling with clang 12.0 and std=c++17 flag works as expected. However, using (2) instead, I obtain
<source>:28:50: error: member pointer refers into non-class type 'double'
static const bool value = helper<T, void (T::*)() const>::value;
^
<source>:32:33: note: in instantiation of template class 'has_print_method<double>' requested here
const bool has_print_method_v = has_print_method<T>::value;
^
<source>:34:39: note: in instantiation of variable template specialization 'has_print_method_v<double>' requested here
template<typename T, std::enable_if_t<has_print_method_v<T>, bool> = true>
^
<source>:35:6: note: while substituting prior template arguments into non-type template parameter [with T = double]
void print(T t) {
^~~~~~~~~~~~
<source>:47:5: note: while substituting deduced template arguments into function template 'print' [with T = double, $1 = (no value)]
print(1.0);
^
1 error generated.
What am I missing here? You can find the example here on godbolt. Edit: Um, I have just noticed that both versions compile without an error with gcc11.1. Strange.
First up, judging by your error messages and my own tests, I think you mean that type trait (1) works and (2) doesn't. On that basis, here is a version of trait (1) that tests for a matching function signature:
template <typename T, typename = int>
struct has_print_method : std::false_type {};
template <typename T>
struct has_print_method<T, decltype(&T::print, 0)> : std::is_same <decltype(&T::print), void (T::*)() const> {};
template <typename T>
const bool has_print_method_v = has_print_method<T>::value;
Live demo

Weird constructor SFINAE error with std::initializer_list

I can't understand why the following code doesn't compile. The error message given by the compiler isn't that helpful either.
Working example:
#include <string>
#include <type_traits>
template <typename T>
struct TIteratorValue {
using Type = typename T::Type;
};
template <typename T>
struct TIteratorValue<T *> {
using Type = T;
};
template <typename T>
using IteratorValue = typename TIteratorValue<T>::Type;
template <typename T>
struct Test {
template <typename V,
std::enable_if_t<std::is_constructible_v<T, V>, int> = 0>
Test(std::initializer_list<V> const list, size_t const x = 0)
: Test(list.begin(), list.end(), x) {}
template <typename I,
std::enable_if_t<std::is_constructible_v<T, IteratorValue<I>>, int> = 0>
// Does not compile!
Test(I begin, I const end, size_t const x = 0) {}
// Compiles!
//Test(I begin, I const end, size_t const x) {}
};
int
main() {
Test<std::string> test({ "a", "b", "c" }, 10);
return 0;
}
Clang's error message:
C:\Users\joaom\Dropbox\++A\so\weird_overloading.cpp:6:24: error: type 'int' cannot be used prior to '::' because it has no members
using Type = typename T::Type;
^
C:\Users\joaom\Dropbox\++A\so\weird_overloading.cpp:15:1: note: in instantiation of template class 'TIteratorValue<int>' requested here
using IteratorValue = typename TIteratorValue<T>::Type;
^
C:\Users\joaom\Dropbox\++A\so\weird_overloading.cpp:25:50: note: in instantiation of template type alias 'IteratorValue' requested here
std::enable_if_t<std::is_constructible_v<T, IteratorValue<I>>, int> = 0>
^
C:\Users\joaom\Dropbox\++A\so\weird_overloading.cpp:27:2: note: while substituting prior template arguments into non-type template parameter
[with I = int]
Test(I begin, I const end, size_t const x = 0) {}
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:\Users\joaom\Dropbox\++A\so\weird_overloading.cpp:35:20: note: while substituting deduced template arguments into function template 'Test'
[with I = int, $1 = (no value)]
Test<std::string> test({ "a", "b", "c" }, 10);
The only int is the argument x but I can't see how's that affecting the code.
Visual Studio gives me the same error too.
template <typename I, std::enable_if_t<std::is_constructible_v<T, IteratorValue<I>>, int> = 0>
// Does not compile!
Test(I begin, I const end, size_t const x = 0) {}
// Compiles!
//Test(I begin, I const end, size_t const x) {}
Both of these functions will not compile if the template is instantiated.
Since your main function attempts to construct a Test from 2 parameters, adding a default value to the third parameter simply means that this function should be considered. It allows the template to be instantiated.
I still can't understand why the following code doesn't compile.
You are defining IteratorValue<I> (and therefore I::type) before checking if I is an iterator.
Use the already defined std::iterator_traits to solve this.
// Formatted for clarity
template <typename I,
std::enable_if_t<
std::is_constructible_v<
T,
typename std::iterator_traits<I>::value_type
>,
int
> = 0>

What kinds of templates may be used with is_detected?

I'm interested in understanding what sorts of templates may be used as the first argument of std::experimental::is_detected and similar detection utilities.
Pasted below is an implementation of is_detected and an attempt at using it to detect whether a type T has a member function named .foo(). The general pattern it uses is to first define a type trait which returns the type of a member function T::foo, if it exists:
template<class T>
struct member_foo_result
{
using type = decltype(std::declval<T>().foo());
};
Then, it defines a shorthand alias:
template<class T>
using member_foo_result_t = typename member_foo_result<T>::type;
The problem is that the shorthand alias seems to be incompatible with is_detected.
Here's the full program, with compiler output:
#include <utility>
#include <type_traits>
#include <iostream>
// implementation of is_detected
template<class...>
using void_t = void;
struct nonesuch
{
nonesuch() = delete;
~nonesuch() = delete;
nonesuch(const nonesuch&) = delete;
void operator=(const nonesuch&) = delete;
};
template<class Default, class AlwaysVoid,
template<class...> class Op, class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
};
template<class Default, template<class...> class Op, class... Args>
struct detector<Default, void_t<Op<Args...>>, Op, Args...>
{
using value_t = std::true_type;
using type = Op<Args...>;
};
template<template<class...> class Op, class... Args>
using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
// returns the result type of T::foo()
template<class T>
struct member_foo_result
{
using type = decltype(std::declval<T>().foo());
};
// shorthand alias for member_foo_result<T>::type
template<class T>
using member_foo_result_t = typename member_foo_result<T>::type;
// detects whether or not the member function T::foo() exists
template<class T>
struct has_foo_member : is_detected<member_foo_result_t, T> {};
struct doesnt_have_foo_member {};
int main()
{
std::cout << "result: " << has_foo_member<doesnt_have_foo_member>::value << std::endl;
}
I expect this program to print "result: 0" at runtime. However, it does not compile correctly:
$ clang -std=c++11 test_is_detected.cpp
test_is_detected.cpp:41:43: error: no member named 'foo' in 'doesnt_have_foo_member'
using type = decltype(std::declval<T>().foo());
~~~~~~~~~~~~~~~~~ ^
test_is_detected.cpp:45:1: note: in instantiation of template class 'member_foo_result<doesnt_have_foo_member>' requested here
using member_foo_result_t = typename member_foo_result<T>::type;
^
test_is_detected.cpp:27:33: note: in instantiation of template type alias 'member_foo_result_t' requested here
struct detector<Default, void_t<Op<Args...>>, Op, Args...>
^
test_is_detected.cpp:33:1: note: during template argument deduction for class template partial specialization 'detector<type-parameter-0-0, void, Op, type-parameter-0-2...>' [with Default = nonesuch, Op =
member_foo_result_t, Args = <doesnt_have_foo_member>]
using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
^
test_is_detected.cpp:50:25: note: in instantiation of template type alias 'is_detected' requested here
struct has_foo_member : is_detected<member_foo_result_t, T> {};
^
test_is_detected.cpp:58:30: note: in instantiation of template class 'has_foo_member<doesnt_have_foo_member>' requested here
std::cout << "result: " << has_foo_member<doesnt_have_foo_member>::value << std::endl;
^
1 error generated.
Am I misusing is_detected?
is_detected is meant to be used with alias templates that cause an error in the immediate context (i.e., SFINAE-friendly) when instantiated with "wrong" arguments.
That means that the substitution failure to be detected needs to occur in the alias template itself, rather than some class template it instantiates:
template<class T>
using member_foo_result_t = decltype(std::declval<T>().foo());

SFINAE enable_if explicit constructor

I'm trying to switch between an explicit and an implicit conversion constructor via enable_if.
My code currently looks like
#include <type_traits>
#include <cstdint>
enum class enabled {};
template <bool B, typename T = void> using enable_if_t = typename std::enable_if<B, T>::type;
template <bool B, typename T = void> using disable_if_t = typename std::enable_if<!B, T>::type;
template <std::intmax_t A> struct SStruct
{
static constexpr std::intmax_t a = A;
};
template <typename T> struct SCheckEnable : std::integral_constant<bool, T::a == 0>
{
};
template <typename U, typename T> class CClass
{
public:
template <typename T2, enable_if_t<SCheckEnable<U>::value, enabled>...> constexpr CClass(T2 v) : val(v) {};
template <typename T2, disable_if_t<SCheckEnable<U>::value, enabled>...> explicit constexpr CClass(T2 v) : val(v) {};
private:
T val;
};
int main()
{
CClass<SStruct<0>, double> a = 1; // should use implicit constructor
CClass<SStruct<1>, double> b = CClass<SStruct<1>, double>(1); // should use explicit constructor
}
The true in the enable_ifs is dependent of the template parameter U
If I try to compile this minimal example with g++ 4.9.1 and --std=c++11 enabled I get the following errors
sfinae.cpp: In substitution of ‘template<bool B, class T> using disable_if_t = typename std::enable_if<(! B), T>::type [with bool B = true; T = enabled]’:
sfinae.cpp:13:52: required from here
sfinae.cpp:7:95: error: no type named ‘type’ in ‘struct std::enable_if<false, enabled>’
template <bool B, typename T = void> using disable_if_t = typename std::enable_if<!B, T>::type;
^
sfinae.cpp:19:68: error: prototype for ‘constexpr CClass<U, T>::CClass(T2)’ does not match any in class ‘CClass<U, T>’
template <typename U, typename T> template <typename T2> constexpr CClass<U, T>::CClass(T2 v) : val(v)
^
sfinae.cpp:13:77: error: candidates are: template<class U, class T> template<class T2, int ...<anonymous> > constexpr CClass<U, T>::CClass(T2)
template <typename T2, disable_if_t<true, enabled>...> explicit constexpr CClass(T2 v);
^
sfinae.cpp:12:67: error: template<class U, class T> template<class T2, enabled ...<anonymous> > constexpr CClass<U, T>::CClass(T2)
template <typename T2, enable_if_t<true, enabled>...> constexpr CClass(T2 v);
^
Any idea how to select between explicit and implicit construction based on parameter U here?
Use
template <class...> struct null_v : std::integral_constant<int, 0> {};
and define the constructors as
template <typename T2,
long = null_v<enable_if_t<SCheckEnable<U>::value, T2>>::value>
constexpr CClass(T2 v) : val(v) {};
template <typename T2,
int = null_v<disable_if_t<SCheckEnable<U>::value, T2>>::value>
explicit constexpr CClass(T2 v) : val(v) {};
Making the argument dependent and actually instantiated.
Demo.
[temp.deduct]/8:
If a substitution results in an invalid type or expression, type
deduction fails. An invalid type or expression is one that would be
ill-formed if written using the substituted arguments.
In your case the error occurs outside of any substitution, so that's not causing a deduction failure but rather makes your code ill-formed.

GCC: template constructor instantiated when copy-constructor needed

In the following example, GCC >= 4.7 instantiates the template constructor (which you can observe by reading the error messages) although only the implicitly generated copy-constructor should be needed.
#include <type_traits>
// 'ambiguous' is ambiguous for 'ambiguous<int, int>'
template<typename A, typename B>
struct ambiguous : std::false_type {};
template<typename T>
struct ambiguous<int, T> : std::true_type {};
template<typename T>
struct ambiguous<T, int> : std::true_type {};
// quantity
template<typename Type>
class quantity
{
public:
quantity() = default;
// Copy-constructor is implicitly created
// Template constructor
template<
typename T,
typename = typename std::enable_if<ambiguous<Type, T>::value>::type
>
quantity(quantity<T>) {}
template<
typename T,
typename = typename std::enable_if<ambiguous<Type, T>::value>::type
>
void set(quantity<T>) {}
};
// main
int main()
{
quantity<int> a;
quantity<float> b;
b.set(a);
}
The above code compiles in GCC < 4.7, clang and MSVS (don't know which version, I used the one from http://rextester.com/runcode). In GCC >= 4.7 compilation fails with the following message:
main.cpp: In substitution of ‘template<class T, class> quantity<Type>::quantity(quantity<T>) [with T = int; <template-parameter-1-2> = <missing>]’:
main.cpp:39:12: required from here
main.cpp:23:9: error: ambiguous class template instantiation for ‘struct ambiguous<int, int>’
typename = typename std::enable_if<ambiguous<Type, T>::value>::type
^
main.cpp:9:8: error: candidates are: struct ambiguous<int, T>
struct ambiguous<int, T> : std::true_type {};
^
main.cpp:12:8: error: struct ambiguous<T, int>
struct ambiguous<T, int> : std::true_type {};
^
main.cpp: In function ‘int main()’:
main.cpp:31:10: error: initializing argument 1 of ‘void quantity<Type>::set(quantity<T>) [with T = int; <template-parameter-2-2> = void; Type = float]’
void set(quantity<T>) {}
So when invoking b.set(a);, GCC apparently looks for a copy constructor and on the way instantiates the template constructor which in turn instantiates ambiguous<int, int> which is (uhm...) ambiguous.
Question: Is GCC right to instantiate the template constructor even though a copy constructor is needed?
gcc is correct.
There are a couple of issues here which unfortunately have become conflated in your question:
First, the behavior of gcc < 4.7 is not fundamentally different; all versions of gcc since (at least) 4.4 reject the very similar program:
struct S;
template<typename, typename> struct U {};
template<typename T> struct U<S, T> {};
template<typename T> struct U<T, S> {};
struct S {
S() = default;
template<typename T, typename = typename U<S, T>::type> S(T) {}
};
int main() {
S a;
S b(a);
}
Note that the only real difference is that the copy-initialization is explicit rather than contained in a function call. Clang accepts this program, by the way.
Next, it's not fundamental to this issue that the copy constructor be involved (rule 12.8p6 in C++11); here's another similar program that gcc (all versions) rejects and clang accepts:
struct S {};
template<typename, typename> struct U {};
template<typename T> struct U<S, T> {};
template<typename T> struct U<T, S> {};
void f(S);
template<typename T> typename U<S, T>::type f(T);
int main() {
S a;
f(a);
}
The difference between clang and gcc is in the application of 14.8.2p8:
[...] [ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the "immediate context" and can result in the program being ill-formed. — end note ]
The ambiguity in the template specialization ambiguous<int, int> is outside the immediate context, so the program is ill-formed. (A supporting argument for this is that template specialization ambiguity does not appear in the succeeding list of reasons for type deduction to fail).
MSVC is different again; it accepts the following program that both clang and gcc reject:
template<typename T> struct U { typedef typename T::type type; };
struct S {
S() = default;
template<typename T, typename = typename U<T>::type> S(T) {}
};
int main() {
S a;
S b(a);
}
This is down to rule 12.8p6:
A declaration of a constructor for a class X is ill-formed if its first parameter is of type (optionally cv-qualified) X and either there are no other parameters or else all other parameters have default arguments. A member function template is never instantiated to produce such a constructor signature.
However, in order to determine whether a member function template instantiation is a constructor ill-formed with regard to 12.8p6, it is necessary to instantiate its declaration (cf. 14.7.1p9). Note that MSVC rejects the following program, so it isn't even consistent:
template<typename T> struct U { typedef typename T::type type; };
struct S {
S() = default;
template<typename T> S(T, typename U<T>::type *p = 0) {}
};
int main() {
S a;
S b(a);
}
This has some highly amusing behavior effects; MSVC accepts the following (ill-formed) program:
template<typename T> struct U { typedef typename T::type type; };
struct S {
S() = default;
template<typename T, typename = typename U<T>::type> S(T) {}
};
template<typename T> typename U<T>::type f(T) { return 0; }
int main() {
S a;
S b(a); // XXX
f(a);
}
However if the copy-initialization S b(a) is commented out, the program is rejected!