SFINAE enable_if explicit constructor - c++

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.

Related

Cannot infer template argument 'T' when a second parameter includes 'T'

Given this template function:
template <
typename T,
typename U,
typename = std::enable_if<
std::is_same_v<U, std::unique_ptr<T>> ||
std::is_same_v<U, std::shared_ptr<T>>>>
T foo(U val) {
if constexpr (std::is_same_v<U, std::unique_ptr<T>>) {
return *val;
}
return *val;
}
I want to call it like this:
int x = foo(std::make_unique<int>(1));
But, it's giving this error:
Candidate template ignored: couldn't infer template argument 'T'
I can fix it by providing the type T explicitly:
int x = foo<int>(std::make_unique<int>(1));
Is it possible to fix it such that it can infer? The type is clearly embedded in the typename U but it does not seem to "propagate" to T.
SFINAE constraints don't affect template argument deduction. Your compiler has to deduce T and U first, without even looking at your enable_if_t<...>.
I'd do something like this:
#include <memory>
#include <type_traits>
namespace impl
{
template <typename A, template <typename...> typename B>
struct specialization_of : std::false_type {};
template <template <typename...> typename T, typename ...P>
struct specialization_of<T<P...>, T> : std::true_type {};
}
template <typename A, template <typename...> typename B>
concept specialization_of = impl::specialization_of<A, B>::value;
template <typename T>
requires specialization_of<T, std::unique_ptr> || specialization_of<T, std::shared_ptr>
auto foo(T val)
{
if constexpr (specialization_of<T, std::unique_ptr>)
{
return *val;
}
return *val;
}
[I also want] std::is_same_v<U, std::function<T()>>
Then you need a specialized template just for this task. Something like:
template <typename T, typename U>
struct foo : std::false_type {};
template <typename T, typename U>
struct foo<std::function<T()>, U> : std::is_same<T, U> {};
Then foo<std::function<int()>, int> is true.

C++ Nesting SFINAE Template Produces Compilation Error

I'm trying to create an addition operator for a custom template class, where the first argument is allowed to be either an instance of my class or a basic numeric type. My operator has a definition similar to the example code below:
#include <type_traits>
template<typename T>
struct MyTemplateStruct {
T val;
};
template<typename T, typename U>
struct MyCommonType {
typedef std::common_type_t<T, U> type;
};
template<typename T, typename U>
using MyCommonTypeT = typename MyCommonType<T, U>::type;
template<typename T, typename U>
MyTemplateStruct<MyCommonTypeT<T, U>> operator +(
MyTemplateStruct<T> const& a, MyTemplateStruct<U> const& b)
{
return { a.val + b.val };
}
template<typename T, typename U>
MyTemplateStruct<MyCommonTypeT<T, U>> operator +(
T const a, MyTemplateStruct<U> const& b)
{
return { a + b.val };
}
int main()
{
MyTemplateStruct<double> a{ 0 }, b{ 0 };
a = a + b;
return 0;
}
My expectation was that due to SFINAE, the compilation error that results from trying to instantiate the second operator definition with T = MyTemplateStruct<double>, U = double would just exclude that template from the list of potential matches. However, when I compile, I'm getting the following error:
/usr/include/c++/7/type_traits: In substitution of ‘template<class ... _Tp> using common_type_t = typename std::common_type::type [with _Tp = {MyTemplateStruct, double}]’:
main.cpp:18:38: required from ‘struct MyCommonType<MyTemplateStruct, double>’
main.cpp:31:39: required by substitution of ‘template<class T, class U> MyTemplateStruct<typename MyCommonType<T, U>::type> operator+(T, const MyTemplateStruct&) [with T = MyTemplateStruct; U = double]’
main.cpp:40:13: required from here
/usr/include/c++/7/type_traits:2484:61: error: no type named ‘type’ in ‘struct std::common_type, double>’
If I directly utilize std::common_type_t in the operator definition, instead of using my wrapper template MyCommonTypeT, then SFINAE works as I expect and there is no error. How can I make the above code compile when I have to wrap the call to std::common_type in another template?
You need to make MyCommonTypeT<T, U> (i.e. MyCommonType<T, U>::type) itself invalid, it's not enough that std::common_type_t<T, U> is invalid when declaring type in MyCommonType. For example you can specialize MyCommonType, when MyTemplateStruct is specified as template argument, type is not declared.
template<typename T, typename U>
struct MyCommonType {
typedef std::common_type_t<T, U> type;
};
template<typename T, typename U>
struct MyCommonType<MyTemplateStruct<T>, U> {};
template<typename T, typename U>
struct MyCommonType<T, MyTemplateStruct<U>> {};
LIVE

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

Is it possible to create a template constructor for which "explicit" is added only for certain template parameters? [duplicate]

Update: conditional explicit has made it into the C++20 draft. more on cppreference
The cppreference std::tuple constructor page has a bunch of C++17 notes saying things like:
This constructor is explicit if and only if std::is_convertible<const Ti&, Ti>::value is false for at least one i
How can one write a constructor that is conditionally explicit? The first possibility that came to mind was explicit(true) but that's not legal syntax.
An attempt with enable_if was unsuccessful:
// constructor is explicit if T is not integral
struct S {
template <typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type>
S(T) {}
template <typename T,
typename = typename std::enable_if<!std::is_integral<T>::value>::type>
explicit S(T) {}
};
with the error:
error: ‘template<class T, class> S::S(T)’ cannot be overloaded
explicit S(T t) {}
The proposal that added that N4387: Improving pair and tuple, revision 3 has an example of how it works:
Consider the following class template A that is intended to be used
as a wrapper for some other type T:
#include <type_traits>
#include <utility>
template<class T>
struct A {
template<class U,
typename std::enable_if<
std::is_constructible<T, U>::value &&
std::is_convertible<U, T>::value
, bool>::type = false
>
A(U&& u) : t(std::forward<U>(u)) {}
template<class U,
typename std::enable_if<
std::is_constructible<T, U>::value &&
!std::is_convertible<U, T>::value
, bool>::type = false
>
explicit A(U&& u) : t(std::forward<U>(u)) {}
T t;
};
The shown constructors both use perfect forwarding and they have
essentially the same signatures except for one being explicit, the
other one not. Furthermore, they are mutually exclusively constrained.
In other words: This combination behaves for any destination type T
and any argument type U like a single constructor that is either
explicit or non-explicit (or no constructor at all).
As Praetorian points out this is exactly how libstdc++ implements it.
If we modify the OPs example accordingly, it also works:
struct S {
template <typename T,
typename std::enable_if< std::is_integral<T>::value, bool>::type = false>
S(T) {}
template <typename T,
typename std::enable_if<!std::is_integral<T>::value, bool>::type = false>
explicit S(T) {}
};
One way that seems to work with most compilers is to add a dummy parameter to one of the functions, to make them slightly different.
// constructor is explicit if T is integral
struct S {
template <typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type>
S(T t) {}
template <typename T,
typename = typename std::enable_if<!std::is_integral<T>::value>::type,
typename dummy = void>
explicit S(T t) {}
};
int main()
{
S s1(7);
S s2("Hello");
}
Compiles with MSVC 2015.

Constructor conditionally marked explicit

Update: conditional explicit has made it into the C++20 draft. more on cppreference
The cppreference std::tuple constructor page has a bunch of C++17 notes saying things like:
This constructor is explicit if and only if std::is_convertible<const Ti&, Ti>::value is false for at least one i
How can one write a constructor that is conditionally explicit? The first possibility that came to mind was explicit(true) but that's not legal syntax.
An attempt with enable_if was unsuccessful:
// constructor is explicit if T is not integral
struct S {
template <typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type>
S(T) {}
template <typename T,
typename = typename std::enable_if<!std::is_integral<T>::value>::type>
explicit S(T) {}
};
with the error:
error: ‘template<class T, class> S::S(T)’ cannot be overloaded
explicit S(T t) {}
The proposal that added that N4387: Improving pair and tuple, revision 3 has an example of how it works:
Consider the following class template A that is intended to be used
as a wrapper for some other type T:
#include <type_traits>
#include <utility>
template<class T>
struct A {
template<class U,
typename std::enable_if<
std::is_constructible<T, U>::value &&
std::is_convertible<U, T>::value
, bool>::type = false
>
A(U&& u) : t(std::forward<U>(u)) {}
template<class U,
typename std::enable_if<
std::is_constructible<T, U>::value &&
!std::is_convertible<U, T>::value
, bool>::type = false
>
explicit A(U&& u) : t(std::forward<U>(u)) {}
T t;
};
The shown constructors both use perfect forwarding and they have
essentially the same signatures except for one being explicit, the
other one not. Furthermore, they are mutually exclusively constrained.
In other words: This combination behaves for any destination type T
and any argument type U like a single constructor that is either
explicit or non-explicit (or no constructor at all).
As Praetorian points out this is exactly how libstdc++ implements it.
If we modify the OPs example accordingly, it also works:
struct S {
template <typename T,
typename std::enable_if< std::is_integral<T>::value, bool>::type = false>
S(T) {}
template <typename T,
typename std::enable_if<!std::is_integral<T>::value, bool>::type = false>
explicit S(T) {}
};
One way that seems to work with most compilers is to add a dummy parameter to one of the functions, to make them slightly different.
// constructor is explicit if T is integral
struct S {
template <typename T,
typename = typename std::enable_if<std::is_integral<T>::value>::type>
S(T t) {}
template <typename T,
typename = typename std::enable_if<!std::is_integral<T>::value>::type,
typename dummy = void>
explicit S(T t) {}
};
int main()
{
S s1(7);
S s2("Hello");
}
Compiles with MSVC 2015.