SFINAE with alias, overloading issue - c++

I have this code :
#include <iostream>
#include <type_traits>
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
using FloatingPoint = T;
template <typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
using Integral = T;
template <typename T> void f(Integral<T>) { std::cout << "integral" << std::endl; }
template <typename T> void f(FloatingPoint<T>) {
std::cout << "floating point" << std::endl;
}
int main() {
f(5);
return 0;
}
This code does not compile :
prog.cc:12:28: error: redefinition of 'f'
template <typename T> void f(FloatingPoint<T>) {
^
prog.cc:10:28: note: previous definition is here
template <typename T> void f(Integral<T>) { std::cout << "integral" << std::endl; }
^
prog.cc:17:3: error: no matching function for call to 'f'
f(5);
^
prog.cc:12:28: note: candidate template ignored: substitution failure [with T = int]
template <typename T> void f(FloatingPoint<T>)
It is kind of weird because it understands that there is a substitution failure, but he does not want to take the first overloading.
So I know two ways to handle with this. The first thing is to put the enable_if either as an argument, or as a return type. Is there other way to handle this in a "pretty" way? Or have I to use one of the two solutions I wrote before?
The goal is to write clean code. I'd love to use concept from C++20 but it seems that it is not possible everywhere yet (I mean GCC, Clang, and MSVC) https://en.cppreference.com/w/cpp/compiler_support. Or this website is not up to date and both MSVC (2019), GCC 10 and clang 9 supports well concept?

I think your problem is that FloatingPoint and Integral directly depend on T. Re-organizing your code you can get the following. You can run the code here: https://onlinegdb.com/By-T4bBfB .
#include <iostream>
#include <type_traits>
template<typename T>
using isFloatingPoint = std::enable_if_t<std::is_floating_point_v<T>, bool>;
template<typename T>
using isIntegral = std::enable_if_t<std::is_integral_v<T>, bool>;
template <typename T, isIntegral<T> = true>
void f(T) {
std::cout << "integral" << std::endl;
}
template <typename T, isFloatingPoint<T> = true>
void f(T) {
std::cout << "floatingPoint" << std::endl;
}
int main() {
f(5);
f(5.0);
}
Alternatively, given that you are using C++17 you can use if constexpr. You say that is not scalable in the comments. In my opinion it is, but most likely I do not understand the constraints you are operating under. For this see https://godbolt.org/z/gLc7YE . if constexpr allows you to have only one function and even different return types pretty easily.
#include <type_traits>
#include <string>
template<typename T>
auto f(T){
if constexpr (std::is_floating_point_v<T>){
return 0.0f;
}
else if constexpr (std::is_integral_v<T>){
return 0;
} else {
return 0.0;
}
}
int main() {
static_assert(std::is_same_v<decltype(f(5)), int>, "ERROR 1");
static_assert(std::is_same_v<decltype(f(5.0)), float>, "ERROR 2");
static_assert(std::is_same_v<decltype(f(std::string{"JJ"})), double>, "ERROR 3");
return 0;
}
Finally I would also to note that having a different return type for the two f() in your code would solve your issue (run here the code: https://onlinegdb.com/By-9HZrMS):
#include <iostream>
#include <type_traits>
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, bool> = true>
using FloatingPoint = T;
template <typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
using Integral = T;
template <typename T> int f(Integral<T>) { std::cout << "integral" << std::endl; return 0;}
template <typename T> float f(FloatingPoint<T>) {
std::cout << "floating point" << std::endl;
return 0.0f;
}
int main() {
f(5);
f(5.0);
return 0;
}

Related

Template member function inside template class, specialize the function [duplicate]

I need to specialize template member function for some type (let's say double). It works fine while class X itself is not a template class, but when I make it template GCC starts giving compile-time errors.
#include <iostream>
#include <cmath>
template <class C> class X
{
public:
template <class T> void get_as();
};
template <class C>
void X<C>::get_as<double>()
{
}
int main()
{
X<int> x;
x.get_as();
}
here is the error message
source.cpp:11:27: error: template-id
'get_as<double>' in declaration of primary template
source.cpp:11:6: error: prototype for
'void X<C>::get_as()' does not match any in class 'X<C>'
source.cpp:7:35: error: candidate is:
template<class C> template<class T> void X::get_as()
What is the problem here, and how can I fix it?
It doesn't work that way. You would need to say the following, but it is not correct
template <class C> template<>
void X<C>::get_as<double>()
{
}
Explicitly specialized members need their surrounding class templates to be explicitly specialized as well. So you need to say the following, which would only specialize the member for X<int>.
template <> template<>
void X<int>::get_as<double>()
{
}
If you want to keep the surrounding template unspecialized, you have several choices. I prefer overloads
template <class C> class X
{
template<typename T> struct type { };
public:
template <class T> void get_as() {
get_as(type<T>());
}
private:
template<typename T> void get_as(type<T>) {
}
void get_as(type<double>) {
}
};
If one is able to used std::enable_if we could rely on SFINAE (substitution failure is not an error)
that would work like so (see LIVE):
#include <iostream>
#include <type_traits>
template <typename C> class X
{
public:
template <typename T,
std::enable_if_t<!std::is_same_v<double,T>, int> = 0>
void get_as() { std::cout << "get as T" << std::endl; }
template <typename T,
std::enable_if_t<std::is_same_v<double,T>, int> = 0>
void get_as() { std::cout << "get as double" << std::endl; }
};
int main() {
X<int> d;
d.get_as<double>();
return 0;
}
The ugly thing is that, with all these enable_if's only one specialization needs to be available for the compiler otherwise disambiguation error will arise. Thats why the default behaviour "get as T" needs also an enable if.
Probably the cleanest way to do this in C++17 and on-wards is to use a if constexpr in combination with the std::is_same_v type trait without explicitly specialisation at all:
#include <iostream>
#include <type_traits>
template <typename C>
class X {
public:
template <typename T>
void get_as() {
// Implementation part for all types
std::cout << "get as ";
// Implementation part for each type separately
if constexpr (std::is_same_v<double, T>) {
std::cout << "'double'";
} else if constexpr (std::is_same_v<int, T>) {
std::cout << "'int'";
} else {
std::cout << "(default)";
}
// Implementation part for all types
std::cout << std::endl;
return;
}
};
int main() {
X<int> d {};
d.get_as<double>(); // 'double'
d.get_as<int>(); // 'int'
d.get_as<float>(); // (default)
return EXIT_SUCCESS;
}
Try it here!
If you need to have a return type as well you could declare the return type as auto:
template <typename T>
auto get_as() {
if constexpr (std::is_same_v<double, T>) {
return 0.5;
} else {
return 0;
}
}

How to implement metaprogramming in c++ without std::enable_if

I was reading somewhere that it would be possible to do c++ metaprogramming using function overloads instead of SFINAE. I came up with a toy exercise to test this. I want to detect if a particular type is a nested vector. My code that works is
#include <type_traits>
#include <vector>
#include <iostream>
template <typename T> struct Tag {};
template <typename TLeaf >
consteval bool is_nested(Tag<TLeaf>)
{
return true;
}
template <typename TLeaf , typename TSub, typename enable = std::enable_if_t< !std::is_same_v<TLeaf, TSub> >>
consteval bool is_nested(Tag<TSub>)
{
return false;
}
template <typename TLeaf , typename TSub>
consteval bool is_nested(Tag<std::vector<TSub>>)
{
return is_nested<TLeaf>(Tag<TSub>{});
}
template <typename TSub, typename TLeaf>
consteval bool is_nested()
{
return is_nested<TLeaf>(Tag<TSub>{});
}
int main(){
using type_nested1 = std::vector<std::string>;
using type_nested2 = std::vector<type_nested1>;
std::cout << is_nested<type_nested1, std::string>() << std::endl;
std::cout << is_nested<type_nested2, std::string>() << std::endl;
std::cout << is_nested<int, std::string>() << std::endl;
std::cout << is_nested<type_nested1, int>() << std::endl;
std::cout << is_nested<type_nested2, int>() << std::endl;
}
https://godbolt.org/z/zGEf9cej5
and the output is
Program returned: 0
Program stdout
1
1
0
0
0
but I'm disappointed that I had to use std::enable_if_t to disambiguate the two overloads. Can this be rewritten to keep the spirit of the exercise but eliminate any SFINAE crud from the solution?
If you just pass two arguments all the way through, you don't need to do anything special:
template <typename T> struct Tag {};
template <typename T> inline constexpr Tag<T> tag;
template <typename T>
consteval bool is_nested(Tag<T>, Tag<T>) {
return true;
}
template <typename L, typename T>
consteval bool is_nested(Tag<L>, Tag<T>) {
return false;
}
template <typename L, typename T>
consteval bool is_nested(Tag<std::vector<L>>, Tag<T>) {
return is_nested(tag<L>, tag<T>);
}
template <typename L, typename T>
consteval bool is_nested() {
return is_nested(tag<L>, tag<T>);
}
Where you can combine the first two overloads into one:
template <typename L, typename T>
consteval bool is_nested(Tag<L>, Tag<T>) {
return std::is_same_v<L, T>;
}
Which was also true of your original example (but it's easier to see if all your parameters are in the same spot):
template <typename TLeaf , typename TSub>
consteval bool is_nested(Tag<TSub>) {
return std::is_same_v<TLeaf, TSub>;
}

A recursive_invoke_result_t template structure

I am trying to implement a recursive version std::invoke_result_t with C++20 concept so that the type of nested invoke result can be retrieved.
The use case of recursive_invoke_result_t
auto f1 = [](int x) { return static_cast<double>(x); };
std::cout << typeid(recursive_invoke_result_t<decltype(f1), int>).name() << std::endl; // expected output "double"
std::cout << typeid(recursive_invoke_result_t<decltype(f1), std::vector<int>>).name() << std::endl; // expected output is something like "std::vector<double>"
std::cout << typeid(recursive_invoke_result_t<decltype(f1), std::vector<std::vector<int>>>).name() << std::endl; // expected output is something like "std::vector<std::vector<double>>"
auto f2 = [](int x) { return std::to_string(x); };
std::cout << typeid(recursive_invoke_result_t<decltype(f2), std::vector<int>>).name() << std::endl; // expected output is something like "std::vector<string>"
auto f3 = [](std::string x) { return std::atoi(x.c_str()); };
std::cout << typeid(recursive_invoke_result_t<decltype(f3), std::vector<std::string>>).name() << std::endl; // expected output is something like "std::vector<int>"
The experimental implementation
The experimental implementation is as below.
// recursive_invoke_result_t implementation
template<typename F, typename T>
requires (std::invocable<F, T>)
struct recursive_invoke_result_t_detail
{
using type = std::invoke_result_t<F, T>;
};
template <typename F, template <typename...> typename Container, typename... Ts>
requires (std::ranges::input_range<std::ranges::range_value_t<Container<Ts...>>>) && (!std::invocable<F, Container<Ts...>>)
struct recursive_invoke_result_t_detail<F, Container<Ts...>>
{
using type = Container<typename recursive_invoke_result_t_detail<F, std::iter_value_t<Container<Ts...>>>::type>;
};
template<typename F, typename T>
using recursive_invoke_result_t = typename recursive_invoke_result_t_detail<F, T>::type;
In the above experimental implementation, the test case recursive_invoke_result_t<decltype(f1), int> seems to be working fine. When it comes to the second test case recursive_invoke_result_t<decltype(f1), std::vector<int>>, the compile errors 'recursive_invoke_result_t_detail': the associated constraints are not satisfied, 'recursive_invoke_result_t' : Failed to specialize alias template and unable to recover from previous error(s); stopping compilation occurred. Is there any way to solve this? Please give me some hints or examples.
This
template<typename F, typename T> requires std::invocable<F, T>
struct recursive_invoke_result_t_detail
{
using type = std::invoke_result_t<F, T>;
};
is the primary template for recursive_invoke_result_t_detail. The requires std::invocable<F, T> then means that recursive_invoke_result_t_detail<F, T> is only valid if std::invocable<F, T>. Period. No specialization can relax this requirement; it is a property of the template itself. The requires constraint on the specialization is just an added constraint: the template can only be instantiated with <F, T> if std::invocable<F, T>, and that partial specialization only applies if, further, T = Container<Ts...> for some Ts... and the other constraint you gave is satisfied (which is actually impossible!)
Leave the primary template unconstrained (and also name and alias it according to convention)
template<typename, typename>
struct recursive_invoke_result { };
template<typename F, typename T>
using recursive_invoke_result_t = recursive_invoke_result<F, T>::type;
Give a partial specialization for the base case
template<typename T, std::invocable<T> F>
struct recursive_invoke_result<F, T> { using type = std::invoke_result_t<F, T>; };
and then your other partial specialization
template<typename F, template<typename...> typename Container, typename... Ts>
requires (
!std::invocable<F, Container<Ts...>> && // don't conflict with base case
std::ranges::input_range<Container<Ts...>> && // no idea why you asked for the contained type to be range, fixed it
requires { typename recursive_invoke_result_t<F, std::ranges::range_value_t<Container<Ts...>>>; }) // SFINAE-compatibility
struct recursive_invoke_result<F, Container<Ts...>> {
using type = Container<recursive_invoke_result_t<F, std::ranges::range_value_t<Container<Ts...>>>>; // not iter_value_t, the container isn't an iterator!
};
Yum.
// typeid(...).name() doesn't produce anything readable for me on Godbolt
template<typename T>
std::ostream &print_type(std::ostream &out) {
// stealing https://stackoverflow.com/a/20170989
// works only for GCC
std::string_view name = __PRETTY_FUNCTION__;
name.remove_prefix(50);
name.remove_suffix(42);
return out << name;
};
template<typename T>
constexpr void assert_no_type() { }
template<typename T> requires requires { typename T::type; }
void assert_no_type() = delete;
int main() {
using F = decltype([](int x) -> double { return x; });
std::cout << "double(int), int: " << print_type<recursive_invoke_result_t<F, int>> << "\n";
std::cout << "double(int), std::vector<int>: " << print_type<recursive_invoke_result_t<F, std::vector<int>>> << "\n";
assert_no_type<recursive_invoke_result<F, std::vector<std::ostream>>>();
std::cout << "double(int), std::vector<std::vector<int>>: " << print_type<recursive_invoke_result_t<F, std::vector<std::vector<int>>>> << "\n";
std::cout << "double(int), std::vector<std::set<int>>: " << print_type<recursive_invoke_result_t<F, std::vector<std::set<int>>>> << "\n";
using G = decltype([](std::vector<int> const &v) -> std::vector<double> { return {}; });
assert_no_type<recursive_invoke_result<G, std::set<std::set<int>>>>();
std::cout << "std::vector<double>(std::vector<int>): " << print_type<recursive_invoke_result_t<G, std::set<std::vector<int>>>> << "\n";
}
Godbolt

Using std::enable_if with anonymous type parameters

I try to use std::enable_if with an unused and unnamed type parameter, in order to not distort the return type. However, the following code does not compile.
#include <iostream>
template <typename T, typename = std::enable_if_t<!std::is_integral<T>::value>>
T foo() { std::cout << "non-integral" << std::endl; return T(); }
template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
T foo() { std::cout << "integral" << std::endl; return T(); }
int main() {
foo<float>();
foo<int>();
}
The compiler says:
7:3: error: redefinition of 'template<class T, class> T foo()'
4:3: note: 'template<class T, class> T foo()' previously declared here
In function 'int main()':
11:12: error: no matching function for call to 'foo()'
11:12: note: candidate is:
4:3: note: template<class T, class> T foo()
4:3: note: template argument deduction/substitution failed:
What is the problem here? How do I have to change the code to get it compile? The text book "Discovering Modern C++" explicitly encourages the use of std::enable_if with anonymous type parameters.
EDIT: I know that it works if I put std::enable_if into the return type. However, my intention is to get some more details why it does not work if I use it with anonymous type parameters. As I said, my text book encourages the variant using anonymous type parameters, so I am wondering why my code does not compile.
However, my intention is to get some more details why it does not work if I use it with anonymous type parameters.
Default values do not participate in overload resolution, thus you are actually redefining the same function.
Let's simplify your example:
template<typename = int>
void f() {}
template<typename = void>
void f() {}
int main() {
f<>();
}
The code above does not compile, for it couldn't know what version of f you want to invoke.
In your case, if I invoke foo as foo<void, void>, I've almost the same problem.
The compiler cannot guess what's my intention and the fact that the second parameter has a default value doesn't mean that you can't pass in a different type.
Because of that, the code is ill-formed and the compiler correctly gives you an error.
As a side note, you can still have it working without using the std::enable_if_t in the return type.
As an example:
#include <type_traits>
#include <iostream>
template <typename T, std::enable_if_t<!std::is_integral<T>::value>* = nullptr>
T foo() { std::cout << "non-integral" << std::endl; return T(); }
template <typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
T foo() { std::cout << "integral" << std::endl; return T(); }
int main() {
foo<float>();
foo<int>();
}
While I tried to figure out what was the (wrong) assumption of the OP and explain why it can be the case, #T.C. correctly pointed the attention out to the actual reason in the comments to this answer.
It's worth to quote his comment to add more details to the answer:
It's not overload resolution; it's declaration matching. There are no two overloads in the first place for any ambiguity to arise. It's two redefinition errors: the function template, and the default template argument.
You can put enable_if into the return type:
template <typename T>
std::enable_if_t<!std::is_integral<T>::value,T>
foo() { std::cout << "non-integral" << std::endl; return T(); }
template <typename T>
std::enable_if_t<std::is_integral<T>::value, T>
foo() { std::cout << "integral" << std::endl; return T(); }
By the way, enable_if_t is available from C++14, so you might want to say typename std::enable_if<std::is_integral<T>::value, T>::type instead. Quite a mouthful.
But a little more idiomatic (and readable) would be to dispatch basing on the type:
template <typename T>
T foo_impl(std::false_type) { std::cout << "non-integral" << std::endl; return T(); }
template <typename T>
T foo_impl(std::true_type) { std::cout << "integral" << std::endl; return T(); }
template <typename T>
T foo(){
return foo_impl<T>(typename std::is_integral<T>::type{});
}
There are a couple of ways you can SFINAE away functions. You should usually refrain from adding an extra function/template parameter and just mingle with the return type.
template <typename T>
auto foo() -> std::enable_if_t<!std::is_integral<T>::value, T>
{ std::cout << "non-integral" << std::endl; return T(); }
template <typename T>
auto foo() -> std::enable_if_t<std::is_integral<T>::value, T>
{ std::cout << "integral" << std::endl; return T(); }
Your error is that you're using enable_if_t on the right of the equal sign.
You have to use it on the left
#include <iostream>
#include <type_traits>
template <typename T, std::enable_if_t<!std::is_integral<T>::value, int> = 0>
T foo() { std::cout << "non-integral" << std::endl; return T(); }
template <typename T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
T foo() { std::cout << "integral" << std::endl; return T(); }
int main() {
foo<float>();
foo<int>();
}
But this work with C++14.
In C++11 (your question is tagged C++11) you don't have enable_if_t.
The code become
#include <iostream>
#include <type_traits>
template <typename T,
typename std::enable_if<!std::is_integral<T>::value, int>::type = 0>
T foo() { std::cout << "non-integral" << std::endl; return T(); }
template <typename T,
typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T foo() { std::cout << "integral" << std::endl; return T(); }
int main() {
foo<float>();
foo<int>();
}

explicit specialization of template class member function

I need to specialize template member function for some type (let's say double). It works fine while class X itself is not a template class, but when I make it template GCC starts giving compile-time errors.
#include <iostream>
#include <cmath>
template <class C> class X
{
public:
template <class T> void get_as();
};
template <class C>
void X<C>::get_as<double>()
{
}
int main()
{
X<int> x;
x.get_as();
}
here is the error message
source.cpp:11:27: error: template-id
'get_as<double>' in declaration of primary template
source.cpp:11:6: error: prototype for
'void X<C>::get_as()' does not match any in class 'X<C>'
source.cpp:7:35: error: candidate is:
template<class C> template<class T> void X::get_as()
What is the problem here, and how can I fix it?
It doesn't work that way. You would need to say the following, but it is not correct
template <class C> template<>
void X<C>::get_as<double>()
{
}
Explicitly specialized members need their surrounding class templates to be explicitly specialized as well. So you need to say the following, which would only specialize the member for X<int>.
template <> template<>
void X<int>::get_as<double>()
{
}
If you want to keep the surrounding template unspecialized, you have several choices. I prefer overloads
template <class C> class X
{
template<typename T> struct type { };
public:
template <class T> void get_as() {
get_as(type<T>());
}
private:
template<typename T> void get_as(type<T>) {
}
void get_as(type<double>) {
}
};
If one is able to used std::enable_if we could rely on SFINAE (substitution failure is not an error)
that would work like so (see LIVE):
#include <iostream>
#include <type_traits>
template <typename C> class X
{
public:
template <typename T,
std::enable_if_t<!std::is_same_v<double,T>, int> = 0>
void get_as() { std::cout << "get as T" << std::endl; }
template <typename T,
std::enable_if_t<std::is_same_v<double,T>, int> = 0>
void get_as() { std::cout << "get as double" << std::endl; }
};
int main() {
X<int> d;
d.get_as<double>();
return 0;
}
The ugly thing is that, with all these enable_if's only one specialization needs to be available for the compiler otherwise disambiguation error will arise. Thats why the default behaviour "get as T" needs also an enable if.
Probably the cleanest way to do this in C++17 and on-wards is to use a if constexpr in combination with the std::is_same_v type trait without explicitly specialisation at all:
#include <iostream>
#include <type_traits>
template <typename C>
class X {
public:
template <typename T>
void get_as() {
// Implementation part for all types
std::cout << "get as ";
// Implementation part for each type separately
if constexpr (std::is_same_v<double, T>) {
std::cout << "'double'";
} else if constexpr (std::is_same_v<int, T>) {
std::cout << "'int'";
} else {
std::cout << "(default)";
}
// Implementation part for all types
std::cout << std::endl;
return;
}
};
int main() {
X<int> d {};
d.get_as<double>(); // 'double'
d.get_as<int>(); // 'int'
d.get_as<float>(); // (default)
return EXIT_SUCCESS;
}
Try it here!
If you need to have a return type as well you could declare the return type as auto:
template <typename T>
auto get_as() {
if constexpr (std::is_same_v<double, T>) {
return 0.5;
} else {
return 0;
}
}