【问题标题】:Force function template/function overload for specific type强制特定类型的函数模板/函数重载
【发布时间】:2013-03-01 23:36:43
【问题描述】:

我有一个函数

foo(int, int, int, int/long long/_int64/double/long double/char *, int ONLY IF previous char * - otherwise unneeded)

问题是,由于需要字符串副本,char * 的实现与值类型不同。是的,这是旧代码的接口,所以我不能使用 std::string :/

目前,我将它作为一个模板,以及一个用于 char * 的函数重载,并带有额外的参数。但是,其他类型的所有操作在 char * 上也是有效的,所以如果调用者忘记了最后一个参数,函数会默默地匹配模板,从而产生错误的逻辑。

有什么方法可以强制在函数模板中使用重载/使用默认参数/允许特定类型的不同签名(额外参数)而无需静默匹配较小的签名?

更多: 尚无法访问 C++11,但我愿意看到使用它来帮助推动采用的建议。 没有提升,但和上面一样

我也试过了

return_type foo(int,int,int,typename std::enable_if<!std::is_pointer<T>::value, T>::type & value2update)

没有任何运气。然后它声称带有双 & 参数的调用无法匹配。

【问题讨论】:

  • “否则不需要”到底是什么意思?这应该可以通过重载轻松实现。
  • 我假设额外的int 是一个长度?您可以使用StringPiece 来完全避免使用特殊外壳吗?
  • @Xeo,是的,我只是想避免每种类型的代码重复,所以想模板化其他类型,它们都共享完全相同的代码。 @ Scott Lamb,是的,可以肯定这是一个有效的——而且可能是最好的——选择。但是,这种集成将发生在代码库中大约 3000 个位置,因此我希望避免在调用代码中添加 StringPiece 创建逻辑。我只是希望我做错了什么来强制专业化 - 必须更彻底地调查 Thomas 的答案
  • 如果您想这样做,您可以查看clang tools 进行自动重构。我听说这种乏味的改变很棒,虽然我自己从未尝试过。

标签: c++ templates overloading


【解决方案1】:

您可以让链接器帮助您。在标头中声明但不定义您的函数模板和char* 重载:

template<typename T>
foo(int, int, int, T);
foo(int, int, int, char*, int);

在实现文件(.cpp/.cc)中,同时实现:

template<typename T>
foo(int, int, int, T) { ... }

foo(int, int, int, char*, int) { ... }

并为您要接受的类型显式实例化版本:

template<>
foo(int, int, int, int);
template<>
foo(int, int, int, long long);
// etc.

如果我理解正确,ScottLamb 在 cmets 中提出了类似的建议。标题:

foo(int, int, int, int);
foo(int, int, int, long long);
...
foo(int, int, int, char*, int);

实施文件(.cpp/.cc):

namespace {
    template<typename T>
    foo_tmpl(int, int, int, T) { ... }
}

foo(int, int, int, int) { foo_tmpl(...); }
foo(int, int, int, long long) { foo_tmpl(...); }
....
foo(int, int, int, char*, int) { ... }

从使用标头的人的角度来看,这是更可取的(他们可以立即看到哪些重载可用),但在实现方面需要做更多的工作。

【讨论】:

  • 恕我直言,如果您列出每个支持的重载,标题会更直接/可读。如果定义很重要,您可以委托给实现文件中匿名命名空间中的模板函数以减少重复。
  • 从外观上看,这是最好的答案。为了清楚起见,是头文件还是 cpp 文件中的显式实例化? @ScottLamb 你愿意解释一下你的评论吗?你是说实例化应该像我刚才提到的那样列在标题中,然后在cpp文件中定义吗?顺便说一句,如果这改变了事情,整个类都在一个命名空间中
  • 更新了我认为他的意思。
  • @ScottLamb,Thomas,我在更新后采用了建议的格式。非常感谢。作为一个快速说明,我的模板函数也使用了一个成员函数。出于这个原因,我不能将它包装在一个匿名命名空间中,对吗?我也必须在标题中私下声明它。
  • 是的,我认为你是对的,你必须在类中声明(可能在标题中)。
【解决方案2】:

使用一个帮助类 string_ref 为您捆绑这两个参数:

class string_ref {
  const char *str;
  std::size_t len;
public:
  string_ref(std::string const& s)
    : str(s.c_str()), len(s.size()) {}

  string_ref(const char *c, std::size_t len)
    : str(c), len(len) {}

  const char * c_str() const { return str; }
  std::size_t size() const { return len; }
};

然后

foo(int, int, int, int/long long/_int64/double/long double/const char*, [int])

只需添加

foo(int, int, int, string_ref)
{
   foo(int, int, int, c_str(), size());
}

string_ref 可以从 const char*int 创建而没有开销,并且可以轻松提取两者以进一步传递给 C 函数。

(命名string_refnot random。)

【讨论】:

  • 这不安全! std::string::c_str() 仅在 string 对象仍然存在且未被修改时才有效。
  • 如果小心使用,我看不出有什么问题。 foo(..., string_ref(std::string("ok"))) 例如应该没问题。
  • 我喜欢这个答案,因为它是一种应该完成的方式,我认为 - 非常接近于仅使用 std::string。此外,我很欣赏关于无开销的评论——这在这种情况下是至关重要的,在其他情况下,更优雅的面向对象的解决方案在分析后为了原始速度而被丢弃。它真的是“没有”开销吗?我看不到如何没有为此 string_ref 对象生成实际的赋值汇编程序指令。
猜你喜欢
  • 1970-01-01
  • 2021-10-30
  • 2017-04-04
  • 1970-01-01
  • 2012-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多