SFINAE :知道函数是否已经存在
SFINAE : Know if a function already exist or no
基本上,我想写这样的代码:
std::vector<float> a = { 54, 25, 32.5 };
std::vector<int> b = { 55, 65, 6 };
std::cout << a << b << std::string("lol");
不可能,因为 operator<<(ostream&, vector)
没有重载
所以,我编写了一个函数来完成这项工作:
template<template<typename...> typename T, typename ...Args>
std::enable_if_t<is_iterable_v<T<Args...>>>, std::ostream> &operator<<(std::ostream &out, T<Args...> const &t) {
for (auto const &e : t)
out << e << " ";
out << std::endl;
return out;
}
效果不错,但我的字符串有问题。因为字符串是可迭代的并且字符串具有 operator<<
函数。
所以我测试了另一个特性,比如 !is_streamable_out && _is_iterable
测试类似的东西:std::declval<std::ostream&>() << std::declval<T>()
以及它是否有开始/结束功能。它在 MSVC 上运行良好,但在 Clang 上运行良好(我认为这是因为编译器也使用了我刚刚创建的函数,所以它发现一个重载可用于所有方法)。
所以,我目前正在使用 !is_same_v<string, T>
,但恕我直言,它并不完美。
有没有办法在不重新声明函数的情况下知道函数是否存在?
基本上,我想做那样的事情
if function foo does not exist for this type.
then function foo for this type is ...
这与 Is it possible to write a template to check for a function's existence? 不是同一个问题,因为在另一个线程中,函数并不完全相同(toString 与 toOptionalString)。就我而言,功能是相同的。
这是我的完整代码:
template <class...>
struct make_void { using type = void; };
template <typename... T>
using void_t = typename make_void<T...>::type; // force SFINAE
namespace detail {
template<typename AlwaysVoid, template<typename...> typename Operator, typename ...Args>
struct _is_valid : std::false_type {};
template<template<typename...> typename Operator, typename ...Args>
struct _is_valid<void_t<Operator<Args...>>, Operator, Args...> : std::true_type { using type = Operator<Args...>; };
}
template<template<typename ...> typename Operator, typename ...Args>
using is_valid = detail::_is_valid<void, Operator, Args...>;
#define HAS_MEMBER(name, ...)\
template<typename T>\
using _has_##name = decltype(std::declval<T>().name(__VA_ARGS__));\
\
template<typename T>\
using has_##name = is_valid<_has_push_back, T>;\
\
template<typename T>\
constexpr bool has_##name##_v = has_##name<T>::value
HAS_MEMBER(push_back, std::declval<typename T::value_type>());
HAS_MEMBER(begin);
HAS_MEMBER(end);
template<typename T>
using is_iterable = std::conditional_t<has_begin_v<T> && has_end_v<T>, std::true_type, std::false_type>;
template<typename T>
constexpr bool is_iterable_v = is_iterable<T>::value;
template<class T, class Stream, class = void>
struct can_print : std::false_type {};
template<class T, class Stream>
struct can_print<T, Stream, void_t<decltype(std::declval<Stream&>() << std::declval<const T&>())>> : std::true_type {};
template<class T, class Stream = std::ostream>
constexpr bool can_print_v = can_print<T, Stream>::value;
template<typename T>
std::enable_if_t<is_iterable_v<T> && !can_print_v<T>, std::ostream> &operator<<(std::ostream &out, T const &t) {
for (auto const &e : t)
out << e << " ";
out << std::endl;
return out;
}
template<typename A, typename B>
std::ostream &operator<<(std::ostream &out, std::pair<A, B> const &p) {
out << p.first << " " << p.second << std::endl;
return out;
}
template<typename T>
std::enable_if_t<has_push_back_v<T>, T> &operator<<(T &c, typename T::value_type const &e) {
c.push_back(e);
return c;
}
和主要的:
#include <iostream>
#include <vector>
#include "Tools/stream.h"
#include <string>
#include <map>
int main() {
std::vector<float> a = { 54, 25, 32.5 };
std::vector<int> b = { 55, 65, 6 };
std::cout << a;
std::cout << has_push_back<float>::value << " " << has_push_back<std::vector<float>>::value << std::endl;
std::cout << is_iterable<std::vector<float>>::value << " " << is_iterable<float>::value << std::endl;
getchar();
return 0;
}
您可以编写一个小的检测习惯用法来测试表达式 stream << value
是否格式正确*。
这里使用的是std::ostream
:
template<class...>
using void_t = void;
template<class T, class Stream, class=void>
struct can_print : std::false_type{};
template<class T, class Stream>
struct can_print<T, Stream, void_t<decltype(std::declval<Stream&>() << std::declval<const T&>())>> : std::true_type{};
template<class T, class Stream=std::ostream>
constexpr bool can_print_v = can_print<T, Stream>::value;
现在您可以像这样为 operator<<
编写重载:
template<template<class...> class C, class...T>
std::enable_if_t<!can_print_v<C<T...>>, std::ostream>& operator<<(std::ostream &out, C<T...> const &t) {
for (auto const &e : t)
out << e << " ";
out << std::endl;
return out;
}
和一个测试
std::vector<float> a = { 54, 25, 32.5 };
std::vector<int> b = { 55, 65, 6 };
std::cout << a;
std::cout << b;
std::cout << std::string("lol") << std::endl;
Demo
Yakk 在. This sentence is false 中指出了一件有趣的事情。
基本上,通过使用 !can_print_v
为 operator<<
启用重载,can_print_v
随后应该是 true
,但它是 false
,因为第一个模板的实例化导致从 std::false_type
派生的结构。因此,can_print_v
的后续测试无效。
我将这个答案作为一个警示故事。特征本身是可以的,但是将它用于 SFINAE 使特征无效的东西是不行的。
*看来 OP 已将此代码复制到他们自己的代码库中,然后修改了问题以包含它,以防您想知道为什么它看起来是相反的
How to avoid this sentence is false in a template SFINAE? 提供了解决您问题的答案——重载 <<(ostream&, Ts...)
,它的优先级低于任何其他 <<
重载。
同时我要说你的计划很糟糕。 std
类型的重载运算符是一个糟糕的计划,原因有两个。
首先,除非有充分的理由,否则您应该不愿意为您不拥有的类型重载运算符。
其次,如果你这样做,你应该在类型的命名空间中进行,并且你不能将你的 <<
注入 namespace std
而不会使你的程序格式错误。
在不同于相关类型的名称空间中重载的运算符只能在您进行重载的名称空间中找到。在与参数相关的名称空间中重载的运算符随处可见。
这导致脆弱 <<
只能在一个命名空间中工作。
所以,改为这样做:
struct print_nothing_t {};
inline std::ostream& operator<<(std::ostream& os, print_nothing_t) {
return os;
}
template<class C, class Sep=print_nothing_t>
struct stream_range_t {
C&& c;
Sep s;
template<class T=print_nothing_t>
stream_range_t( C&& cin, T&& sin = {} ):
c(std::forward<C>(cin)),
s(std::forward<T>(sin))
{}
friend std::ostream& operator<<(std::ostream& os, stream_range_t&& self) {
bool first = true;
for (auto&& x:self.c) {
if (!first) os << self.s;
os << decltype(x)(x);
first = false;
}
return os;
}
};
template<class C, class Sep = print_nothing_t>
stream_range_t<C, Sep> stream_range( C&& c, Sep&& s={} ) {
return {std::forward<C>(c), std::forward<Sep>(s)};
}
现在,如果您希望嵌套向量起作用,则必须实现一个流范围,将适配器应用于其内容。
Live example 用这个测试代码:
std::vector<int> v{1,2,3,4};
std::cout << stream_range(v, ',') << "\n";
输出为 1,2,3,4
.
如何使这个递归:
我们可以编写一个 adapt_for_streaming
函数来分派给 identity
(对于已经流式传输的事物),对于可迭代但尚未流式传输的事物分派给 stream_range
。
然后用户可以为其他类型添加新的 adapt_for_streaming
重载。
stream_range_t
然后在其内容上使用 adapt_for_streaming
进行流式传输。
接下来,我们可以将 tuple/pair/array 重载添加到 adapt_for_streaming
,突然 std::vector< std::vector< std::tuple<std::string, int> > >
就可以流式传输了。
最终用户可以直接调用 stream_range
或 adapt_for_streaming
来串接一个可迭代的容器。您甚至可以直接在 std::string
上调用 stream_range
,将其视为 char
的可流式集合而不是字符串。
基本上,我想写这样的代码:
std::vector<float> a = { 54, 25, 32.5 };
std::vector<int> b = { 55, 65, 6 };
std::cout << a << b << std::string("lol");
不可能,因为 operator<<(ostream&, vector)
所以,我编写了一个函数来完成这项工作:
template<template<typename...> typename T, typename ...Args>
std::enable_if_t<is_iterable_v<T<Args...>>>, std::ostream> &operator<<(std::ostream &out, T<Args...> const &t) {
for (auto const &e : t)
out << e << " ";
out << std::endl;
return out;
}
效果不错,但我的字符串有问题。因为字符串是可迭代的并且字符串具有 operator<<
函数。
所以我测试了另一个特性,比如 !is_streamable_out && _is_iterable
测试类似的东西:std::declval<std::ostream&>() << std::declval<T>()
以及它是否有开始/结束功能。它在 MSVC 上运行良好,但在 Clang 上运行良好(我认为这是因为编译器也使用了我刚刚创建的函数,所以它发现一个重载可用于所有方法)。
所以,我目前正在使用 !is_same_v<string, T>
,但恕我直言,它并不完美。
有没有办法在不重新声明函数的情况下知道函数是否存在?
基本上,我想做那样的事情
if function foo does not exist for this type.
then function foo for this type is ...
这与 Is it possible to write a template to check for a function's existence? 不是同一个问题,因为在另一个线程中,函数并不完全相同(toString 与 toOptionalString)。就我而言,功能是相同的。
这是我的完整代码:
template <class...>
struct make_void { using type = void; };
template <typename... T>
using void_t = typename make_void<T...>::type; // force SFINAE
namespace detail {
template<typename AlwaysVoid, template<typename...> typename Operator, typename ...Args>
struct _is_valid : std::false_type {};
template<template<typename...> typename Operator, typename ...Args>
struct _is_valid<void_t<Operator<Args...>>, Operator, Args...> : std::true_type { using type = Operator<Args...>; };
}
template<template<typename ...> typename Operator, typename ...Args>
using is_valid = detail::_is_valid<void, Operator, Args...>;
#define HAS_MEMBER(name, ...)\
template<typename T>\
using _has_##name = decltype(std::declval<T>().name(__VA_ARGS__));\
\
template<typename T>\
using has_##name = is_valid<_has_push_back, T>;\
\
template<typename T>\
constexpr bool has_##name##_v = has_##name<T>::value
HAS_MEMBER(push_back, std::declval<typename T::value_type>());
HAS_MEMBER(begin);
HAS_MEMBER(end);
template<typename T>
using is_iterable = std::conditional_t<has_begin_v<T> && has_end_v<T>, std::true_type, std::false_type>;
template<typename T>
constexpr bool is_iterable_v = is_iterable<T>::value;
template<class T, class Stream, class = void>
struct can_print : std::false_type {};
template<class T, class Stream>
struct can_print<T, Stream, void_t<decltype(std::declval<Stream&>() << std::declval<const T&>())>> : std::true_type {};
template<class T, class Stream = std::ostream>
constexpr bool can_print_v = can_print<T, Stream>::value;
template<typename T>
std::enable_if_t<is_iterable_v<T> && !can_print_v<T>, std::ostream> &operator<<(std::ostream &out, T const &t) {
for (auto const &e : t)
out << e << " ";
out << std::endl;
return out;
}
template<typename A, typename B>
std::ostream &operator<<(std::ostream &out, std::pair<A, B> const &p) {
out << p.first << " " << p.second << std::endl;
return out;
}
template<typename T>
std::enable_if_t<has_push_back_v<T>, T> &operator<<(T &c, typename T::value_type const &e) {
c.push_back(e);
return c;
}
和主要的:
#include <iostream>
#include <vector>
#include "Tools/stream.h"
#include <string>
#include <map>
int main() {
std::vector<float> a = { 54, 25, 32.5 };
std::vector<int> b = { 55, 65, 6 };
std::cout << a;
std::cout << has_push_back<float>::value << " " << has_push_back<std::vector<float>>::value << std::endl;
std::cout << is_iterable<std::vector<float>>::value << " " << is_iterable<float>::value << std::endl;
getchar();
return 0;
}
您可以编写一个小的检测习惯用法来测试表达式 stream << value
是否格式正确*。
这里使用的是std::ostream
:
template<class...>
using void_t = void;
template<class T, class Stream, class=void>
struct can_print : std::false_type{};
template<class T, class Stream>
struct can_print<T, Stream, void_t<decltype(std::declval<Stream&>() << std::declval<const T&>())>> : std::true_type{};
template<class T, class Stream=std::ostream>
constexpr bool can_print_v = can_print<T, Stream>::value;
现在您可以像这样为 operator<<
编写重载:
template<template<class...> class C, class...T>
std::enable_if_t<!can_print_v<C<T...>>, std::ostream>& operator<<(std::ostream &out, C<T...> const &t) {
for (auto const &e : t)
out << e << " ";
out << std::endl;
return out;
}
和一个测试
std::vector<float> a = { 54, 25, 32.5 };
std::vector<int> b = { 55, 65, 6 };
std::cout << a;
std::cout << b;
std::cout << std::string("lol") << std::endl;
Demo
Yakk 在
基本上,通过使用 !can_print_v
为 operator<<
启用重载,can_print_v
随后应该是 true
,但它是 false
,因为第一个模板的实例化导致从 std::false_type
派生的结构。因此,can_print_v
的后续测试无效。
我将这个答案作为一个警示故事。特征本身是可以的,但是将它用于 SFINAE 使特征无效的东西是不行的。
*看来 OP 已将此代码复制到他们自己的代码库中,然后修改了问题以包含它,以防您想知道为什么它看起来是相反的
How to avoid this sentence is false in a template SFINAE? 提供了解决您问题的答案——重载 <<(ostream&, Ts...)
,它的优先级低于任何其他 <<
重载。
同时我要说你的计划很糟糕。 std
类型的重载运算符是一个糟糕的计划,原因有两个。
首先,除非有充分的理由,否则您应该不愿意为您不拥有的类型重载运算符。
其次,如果你这样做,你应该在类型的命名空间中进行,并且你不能将你的 <<
注入 namespace std
而不会使你的程序格式错误。
在不同于相关类型的名称空间中重载的运算符只能在您进行重载的名称空间中找到。在与参数相关的名称空间中重载的运算符随处可见。
这导致脆弱 <<
只能在一个命名空间中工作。
所以,改为这样做:
struct print_nothing_t {};
inline std::ostream& operator<<(std::ostream& os, print_nothing_t) {
return os;
}
template<class C, class Sep=print_nothing_t>
struct stream_range_t {
C&& c;
Sep s;
template<class T=print_nothing_t>
stream_range_t( C&& cin, T&& sin = {} ):
c(std::forward<C>(cin)),
s(std::forward<T>(sin))
{}
friend std::ostream& operator<<(std::ostream& os, stream_range_t&& self) {
bool first = true;
for (auto&& x:self.c) {
if (!first) os << self.s;
os << decltype(x)(x);
first = false;
}
return os;
}
};
template<class C, class Sep = print_nothing_t>
stream_range_t<C, Sep> stream_range( C&& c, Sep&& s={} ) {
return {std::forward<C>(c), std::forward<Sep>(s)};
}
现在,如果您希望嵌套向量起作用,则必须实现一个流范围,将适配器应用于其内容。
Live example 用这个测试代码:
std::vector<int> v{1,2,3,4};
std::cout << stream_range(v, ',') << "\n";
输出为 1,2,3,4
.
如何使这个递归:
我们可以编写一个 adapt_for_streaming
函数来分派给 identity
(对于已经流式传输的事物),对于可迭代但尚未流式传输的事物分派给 stream_range
。
然后用户可以为其他类型添加新的 adapt_for_streaming
重载。
stream_range_t
然后在其内容上使用 adapt_for_streaming
进行流式传输。
接下来,我们可以将 tuple/pair/array 重载添加到 adapt_for_streaming
,突然 std::vector< std::vector< std::tuple<std::string, int> > >
就可以流式传输了。
最终用户可以直接调用 stream_range
或 adapt_for_streaming
来串接一个可迭代的容器。您甚至可以直接在 std::string
上调用 stream_range
,将其视为 char
的可流式集合而不是字符串。