简体   繁体   English

替换失败的模板特化

[英]Template specialisation on substitution failure

Consider this function:考虑这个 function:

template<typename T>
void f(T c) {
    std::cout<<c<<std::endl;
}

You see that it will not compile for types which does not have an operator<< overload.您会看到它不会为没有operator<<重载的类型编译。 Now I want to write a function that acts like a fallback for this case.现在我想写一个 function 来作为这种情况的后备。

/*Fallback*/
template<>
void f(T c) {
    std::cout<<"Not Printing"<<std::endl;
}

How must this function be defined to do the job?这个 function 必须如何定义才能完成这项工作?

You have several options of doing this.您有多种选择 Arguably the most elegant way is to define your own type trait (similar to the ones in type_traits ) .可以说最优雅的方式是定义自己的类型特征(类似于type_traits中的那些)

Let's define a is_streamable type trait .让我们定义一个is_streamable类型 trait It takes two template arguments: S is the data type of the file stream (eg std::ostream or std::fstream or any other type that defines a custom streaming operator that is compatible with T ) and secondly the data type of the object to be streamed into this file stream T :它需要两个模板 arguments: S是文件 stream 的数据类型(例如std::ostreamstd::fstream或定义与T兼容的自定义流操作符的任何其他类型),其次是 ZA8CFFDE6331CBD4B666AC 的数据类型要流式传输到此文件 stream T

template<typename S, typename T, typename = void>
struct is_streamable : std::false_type {
};

template<typename S, typename T>
struct is_streamable<S, T, decltype(std::declval<S&>() << std::declval<T&>(), void())> : std::true_type {
};

So far this type trait compiles with C++11 and onwards.到目前为止,此类型特征与 C++11 及更高版本一起编译。 For C++14 and later we can create a convenient alias for it similar to other type traits in C++17:对于 C++14 及以后我们可以为它创建一个方便的别名,类似于 C++17 中的其他类型特征:

template <typename S, typename T>
static constexpr is_streamable_v = is_streamable<S,T>::value;

This type trait will now be the basis for the next step which will make use of SFINAE (C++11 onwards), constexpr if (C++17 onwards) or concepts (C++20).这种类型特征现在将成为下一步使用SFINAE (C++11 及以上)、 constexpr if (C++17 及以上)或concepts (C++20)的基础。

  • In C++11 you could achieve this with either by putting the different implementations into partial specialisations of the same struct and call it with a helper function:C++11 中,您可以通过将不同的实现放入同一结构的部分特化中并使用助手 function 调用它来实现此目的:

     class f_imp { }; template <typename T> class f_imp<T,true> { public: static constexpr void imp(T c) { std::cout << "streamable: " << c << std::endl; } }; template <typename T> class f_imp<T,false> { public: static constexpr void imp(T c) { std::cout << "not streamable" << std::endl; } }; template <typename T> void f(T c) { return f_imp<T,is_streamable<std::ostream,T>::value>::imp(c); }

    Try it here!在这里试试!

    Alternatively you could apply SFINAE either by adding a second input parameter or applying it to the return type:或者,您可以通过添加第二个输入参数或将其应用于返回类型来应用SFINAE

     template<typename T, typename std::enable_if<is_streamable<std::ostream,T>::value>::type* = nullptr> void f(T t) { std::cout << "streamable" << std::endl; } template<typename T, typename std::enable_if<:is_streamable<std:,ostream:T>::value>::type* = nullptr> void f(T t) { std::cout << "not streamable" << std:;endl; }

    Try it here!在这里试试!

  • In C++17 you can actually use a constexpr if to avoid adding a second template argument and overloading of the function altogether.C++17 中,您实际上可以使用constexpr if来避免添加第二个模板参数和完全重载 function。 You can insert all the code inside the function and use if constexpr in combination with std::is_same_v and our is_streamable_v to decide at compile time which branch of our code each template type should take.您可以在 function 中插入所有代码,并结合使用if constexprstd::is_same_v和我们的is_streamable_v来在编译时决定每个模板类型应该采用我们的代码的哪个分支。 This is in particular convenient if adding two specialisations would result in duplicate code but it might be harder to read.如果添加两个特化会导致重复代码但可能更难阅读,这将特别方便。

     template<typename T> void f(T c) { if constexpr (is_streamable_v<std::ostream,T>) { std::cout << "streamable:" << c << std::endl; } else { // Fallback std::cerr << "not streamable" << std::endl; } return; }

    Try it here!在这里试试!

  • Finally in C++20 you could use this type trait to define a concepts such as streamable and not_streamable :最后,在C++20中,您可以使用这种类型特征来定义诸如streamablenot_streamable类的概念

     template <typename T> concept streamable = is_streamable_v<std::ostream,T>; template <typename T> concept not_streamable =;streamable<T>;

    Then you can go on to apply them to your two overloads of the functions然后您可以 go 将它们应用于您的两个函数重载

    template <streamable T> void f(T c) { std::cout << "streamable: " << c << std::endl; } template <not_streamable T> void f(T c) { std::cout << "not streamable" << std::endl; }

    Try it here!在这里试试!

Be aware that you will have to also apply the same logic to any custom streaming operator of a templated class , eg of a templated vector.请注意,您还必须将相同的逻辑应用于模板化 class 的任何自定义流操作符,例如模板化向量。 Instead of declaring the operator for any template parameter typename T you would have to only declare it for streamable element types only.您不必为任何模板参数类型名typename T声明运算符,而只需为可流式元素类型声明它。 In C++20 for example with said streamable concept:例如,在 C++20 中,具有上述streamable概念:

template <streamable T>
std::ostream& operator << (std::ostream& os, std::vector<T> const& vec) {
  for (auto const& v: vec) {
    os << v << " ";
  }
  return os;
}

Otherwise - as the template argument to the is_streamable operator is std::vector<T> as a whole - the compiler sees the operator << for std::vector<T> without checking if it would result in a compilation error for an unstreamable type T which does not define the operator << itself.否则 - 由于is_streamable运算符的模板参数是std::vector<T>作为一个整体 - 编译器看到std::vector<T>operator <<而不检查它是否会导致不可流式的编译错误类型T不定义operator <<本身。

Try it here!在这里试试!

Pre-C++20 C++20 之前

To have these overloads work in a fallback way, we can start by defining a trait that detects the validity of the expression involving operator <<为了让这些重载以备用方式工作,我们可以从定义一个特征开始,该特征检测涉及operator <<的表达式的有效性

namespace detail {
template<typename T, typename = void>
struct streamable : std::false_type{};

template<typename T>
struct streamable<T, decltype(std::declval<std::ostream&>() << std::declval<T&>(), void())> : std::true_type {};
}

It's just your typical use of the detection idiom with as little extra library support as possible.这只是您对检测习语的典型用法,并尽可能少地支持额外的库。 Depending on the standard you are building against, this may be written in other ways (for instance std::void_t can be used, if available).根据您构建的标准,这可能会以其他方式编写(例如,可以使用std::void_t ,如果可用)。

Now, the two overloads can be specified rather simply:现在,可以相当简单地指定这两个重载:

template<typename T>
auto f(T c) -> std::enable_if_t<detail::streamable<T>::value, void> {
    std::cout<<c<<std::endl;
}

template<typename T>
auto f(T c) -> std::enable_if_t<!detail::streamable<T>::value, void> {
    /// other code
}

Post C++20, concepts and constraints make it a whole lot easier.在 C++20 之后,概念和约束使它变得更加容易。 It can even be written ad-hoc:它甚至可以临时编写:

template<typename T>
requires requires(std::ostream& os, T& c) { os << c; }
void f(T c) {
    std::cout<<c<<std::endl;
}

template<typename T> // No extra step, subsumed by the above when possible
void f(T c) {
    // other code
}

With concepts (C++20), we can achieve this like so:使用概念(C++20),我们可以这样实现:

template<typename T>
concept Streamable = requires(T t){std::declval<std::ostream&>() << t; };

template<Streamable T>
void f(T c) { std::cout << c << std::endl; }

/*Fallback*/
template<typename T>
void f(T c) { std::cout << "fallback" < <std::endl; }

Demo演示


Test:测试:

struct Foo{};

int main()
{
    Foo foo;
    f(foo); // prints "fallback"

    int a = 42;
    f(a); // prints "42"
}

If you want to make doubly sure that your fallback will only happen if your type is not Streamable , you can constrain it, too:如果您想双重确保只有在您的类型不是Streamable时才会发生回退,您也可以对其进行约束:

template<typename T> requires (!Streamable<T>)
void f(T c) { /*...*/ }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM