具有第二种模板类型的部分模板特化

Partial template specialization with second template type

它是我真实问题的简化版本。为什么在第一种情况下模板专业化不起作用?

如果我交换前两个函数,它就会工作。

奇怪,但它适用于 msvc 19...

#include <string>
#include <vector>
#include <iostream>

template<typename T, typename M>
void write_impl(T &result, M m) {
    for (const auto &i : result) {
        write_impl(i, m);
    }
}

template<typename M>
void write_impl(const std::string &result, M m) {
    std::cout << result;
}

template<typename T>
void write_impl_1(T &result) {
    for (const auto &i : result) {
        write_impl_1(i);
    }
}

template<>
void write_impl_1(const std::string &result) {
    std::cout << result;
}

int main() {
    std::vector<std::string> a{"42", "43", "44"};
    write_impl(a, 42); // compile time error
    write_impl_1(a); // works fine
}

Godbolt link

首先,有趣的发现!花了我一点时间。

其次,函数模板不存在偏特化

完全特化是可以的,所以第二次实现会选择特化。

第一种情况是两个模板函数之间的重载决议,将选择更专业的。编译器将首先构造重载列表 - 包含调用的候选者。

就是这个东西, void write_impl(const std::string &result, M m) 不会考虑,因为还没有定义!实例化的点直接在模板的定义之后。因此,只有第一个模板在重载集中,它将匹配,因为字符串是可迭代的,并且实例化将失败,因为 char 不是。

这将引发关于 foo 的相同错误:

#include <string>
#include <vector>
#include <iostream>

template<typename T>
void bar(T &result) {
        foo();
}

void foo(){}

int main() {
    std::vector<std::string> a{"42", "43", "44"};
    bar(a);
}

为什么第二种情况有效呢?因为编译器会“向前看”以查看所有可能的特化。但是它们仍然必须出现在实例化专业化的地方。因此,我认为第二种情况是基于 的未定义行为。我对C++的这个黑暗角落不是很精通,我在这里可能是错的。

为什么 MSCV 有效?打败我,它有时是“特殊的”,也许他们的实例化点是错误的,foo 也会错误地工作。

第一种情况不是模板特化;这是函数重载。

不起作用,因为第一个函数调用了第二个未声明的函数。如您所见,切换顺序是有效的,因为第一个(现在是第二个)知道第二个(现在是第一个)的声明(以及定义)。

不能对 C++ 中的函数进行模板偏特化;仅完全专业化。

你真的需要一种偏特化,你可以通过一个 class/struct 和它里面的一个函数。您可以部分特化 struct/class.

例如

#include <string>
#include <vector>
#include <iostream>

template <typename T, typename M>
struct foo
 {
   static void bar (T & result, M m)
    {
      for (const auto &i : result)
         foo<decltype(i), M>::bar(i, m);
    }
 };

template <typename M>
struct foo<std::string const &, M>
 {
   static void bar (std::string const & result, M)
    { std::cout << result; }
 };


int main()
 {
   std::vector<std::string> a{"42", "43", "44"};

   foo<decltype(a), int>::bar(a, 42);
 }

但是,如您所见,并不是很方便。

显然,如果您不需要函数模拟的部分特化,并且您对不同的和重载的模板函数感到满意(假设它们相互调用),您可以声明第一个,声明和定义第二个并定义第一个;

template <typename M>
void write_impl (std::string const &, M);

template<typename T, typename M>
void write_impl (T & result, M m)
 {
    for (const auto &i : result)
        write_impl(i, m);
 }

template <typename M>
void write_impl (std::string const & result, M m)
 { std::cout << result; }