SFINAE:知道功能是否已存在或不存在

Ant*_*ier 8 c++ sfinae c++11

基本上,我想编写这样的代码:

std::vector<float> a = { 54, 25, 32.5 };
std::vector<int> b = { 55, 65, 6 };

std::cout << a << b << std::string("lol");
Run Code Online (Sandbox Code Playgroud)

这是不可能的,因为没有过载 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;
}
Run Code Online (Sandbox Code Playgroud)

这很好用,但我的字符串有问题.因为字符串是可迭代的,字符串HAVE 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 ...
Run Code Online (Sandbox Code Playgroud)

问题是否可以编写模板以检查函数是否存在?因为在这个其他线程中,函数不完全相同(toString vs 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;
}
Run Code Online (Sandbox Code Playgroud)

主要:

#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;
}
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 4

如何避免这句话在模板 SFINAE 中为 false?提供了一个解决您的问题的答案 - 过载<<(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)};
}
Run Code Online (Sandbox Code Playgroud)

现在,如果您希望嵌套向量工作,则必须实现一个流范围,将适配器应用于其内容。

带有此测试代码的实时示例:

std::vector<int> v{1,2,3,4};
std::cout << stream_range(v, ',') << "\n";
Run Code Online (Sandbox Code Playgroud)

输出是1,2,3,4.


如何进行递归:

我们可以编写一个adapt_for_streaming函数,分派到其中一个identity(对于已经可流化的事物),以及对于stream_range可迭代但尚未可流化的事物。

然后,用户可以adapt_for_streaming为其他类型添加新的重载。

stream_range_t然后adapt_for_streaming对其内容进行流式传输。

接下来,我们可以添加元组/对/数组重载adapt_for_streaming,然后突然std::vector< std::vector< std::tuple<std::string, int> > >可以进行流式传输。

最终用户可以stream_range直接调用 或adapt_for_streaming来字符串化可迭代容器。您甚至可以stream_range直接调用 astd::string将其视为可流式集合char而不是字符串。