本文介绍了替换失败的模板特化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑这个函数:

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.Now I want to write a function that acts like a fallback for this case.

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

必须如何定义这个函数才能完成这项工作?

How must this function be defined to do the job?

推荐答案

您有多种选择来执行此操作.可以说最优雅的方法是定义你的 自己的 类型特征(类似于type_traits).

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).

让我们定义一个 is_streamable 类型特征.它需要两个模板参数: S 是文件流的数据类型(例如 std::ostreamstd::fstream 或任何其他定义与 T 兼容的自定义流操作符的类型,其次是要流式传输到此文件流中的对象的数据类型 T:

Let's define a is_streamable type trait. It takes two template arguments: S is the data type of the file stream (e.g. 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:

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 {
};

到目前为止,这个类型特征是用 C++11 及以后的版本编译的.对于 C++14 及更高版本,我们可以为它创建一个方便的别名,类似于 C++17 中的其他类型特征:

So far this type trait compiles with C++11 and onwards. For C++14 and later we can create a convenient alias for it similar to other type traits in C++17:

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

此类型特征现在将成为下一步的基础,该步骤将使用 SFINAE(C++11 以上)、constexpr if(C++17或概念 (C++20).

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).

  • C++11 中,您可以通过将不同的实现放入同一结构的部分特化中并使用辅助函数调用它来实现这一点:

  • 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:

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);
}

在这里试试!

或者,您可以通过添加第二个输入参数或将其应用于返回类型来应用 SFINAE:

Alternatively you could apply SFINAE either by adding a second input parameter or applying it to the return type:

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;
}

在这里试试!

C++17 中,您实际上可以使用 constexpr if 来避免添加第二个模板参数和重载功能齐全.您可以在函数中插入所有代码,并使用 if constexpr 结合 std::is_same_v 和我们的 is_streamable_v 在编译时决定哪个我们每个模板类型应该采用的代码分支.如果添加两个特化会导致重复代码但可能更难阅读,这将特别方便.

In C++17 you can actually use a constexpr if to avoid adding a second template argument and overloading of the function altogether. 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. 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;
}

在这里试试!

最后在 C++20 中,您可以使用此类型特征来定义概念,例如 streamablenot_streamable:

Finally in C++20 you could use this type trait to define a concepts such as streamable and not_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

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;
}

在这里试试!

请注意,您还必须将相同的逻辑应用于模板化类的任何自定义流操作符,例如模板化向量.不必为任何模板参数 typename T 声明运算符,您只需为可流式元素类型声明它.例如在 C++20 中,带有 streamable 概念:

Be aware that you will have to also apply the same logic to any custom streaming operator of a templated class, e.g. of a templated vector. Instead of declaring the operator for any template parameter typename T you would have to only declare it for streamable element types only. In C++20 for example with said streamable concept:

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

否则 - 因为 is_streamable 运算符的模板参数是 std::vector<T> - 编译器会看到 operator << for std::vector 不检查它是否会导致未定义 的不可流类型 T 的编译错误运算符<<本身.

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.

在这里试试!

这篇关于替换失败的模板特化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 05:46