Typesafe可变参数函数

Mar*_*ica 16 c++ templates variadic-templates c++11 c++17

我想编写一个接受可变数量的字符串文字的函数.如果我用C语写作,我必须写一些类似于:

void foo(const char *first, ...);
Run Code Online (Sandbox Code Playgroud)

然后调用看起来像:

foo( "hello", "world", (const char*)NULL );
Run Code Online (Sandbox Code Playgroud)

感觉它应该可以在C++中做得更好.我想出的最好的是:

template <typename... Args>
void foo(const char* first, Args... args) {
    foo(first);
    foo(args);
}

void foo(const char* first) { /* Do actual work */ }
Run Code Online (Sandbox Code Playgroud)

被称为:

foo("hello", "world");
Run Code Online (Sandbox Code Playgroud)

但是我担心递归本质,以及我们在得到单个参数之前不进行任何类型检查的事实,如果有人调用,会使错误混乱foo("bad", "argument", "next", 42).我想要写的,是这样的:

void foo(const char* args...) {
    for (const char* arg : args) {
        // Real work
    }
}
Run Code Online (Sandbox Code Playgroud)

有什么建议?

编辑:还有选项void fn(std::initializer_list<const char *> args),但这使得foo({"hello", "world"});我想避免的呼叫.

lll*_*lll 15

我想你可能想要这样的东西:

template<class... Args,
    std::enable_if_t<(std::is_same_v<const char*, Args> && ...), int> = 0>
void foo(Args... args ){
    for (const char* arg : {args...}) {
        std::cout << arg << "\n";
    }
}

int main() {
    foo("hello", "world");
}
Run Code Online (Sandbox Code Playgroud)

  • @Deduplicator然后使用`std :: is_convertible_v`.目前尚不清楚他们是否想要隐式转换.甚至不清楚他们是否只想要一个硬错误或SFINAE.这就是为什么我的答案开始于*我认为你可能想要这样的东西* (3认同)

Nev*_*vin 8

注意:不能仅匹配字符串文字.你最接近的是匹配一个const char数组.

要进行类型检查,请使用带有const char数组的函数模板.

要使用基于范围的循环遍历它们for,我们需要将其转换为initializer_list<const char*>.我们可以在基于范围的for语句中直接使用大括号,因为数组会衰减到指针.

以下是函数模板的外观(注意:这适用于零个或多个字符串文字.如果需要一个或多个,请更改函数签名以至少使用一个参数.):

template<size_t N>
using cstring_literal_type = const char (&)[N];

template<size_t... Ns>
void foo(cstring_literal_type<Ns>... args)
{
    for (const char* arg : {args...})
    {
        // Real work
    }
}
Run Code Online (Sandbox Code Playgroud)


joe*_*hip 4

虽然所有其他答案都可以解决问题,但您还可以执行以下操作:

\n\n
namespace detail\n{\n    void foo(std::initializer_list<const char*> strings);\n}\n\ntemplate<typename... Types>\nvoid foo(const Types... strings)\n{\n    detail::foo({strings...});\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这种方法似乎(至少对我来说)比使用 SFINAE 更具可读性并且适用于 C++11。此外,它允许您将实现移动foocpp文件中,这也可能很有用。

\n\n

编辑:至少在 GCC 8.1 中,当使用非const char*参数调用时,我的方法似乎会产生更好的错误消息:

\n\n
foo("a", "b", 42, "c");\n
Run Code Online (Sandbox Code Playgroud)\n\n

该实现编译为:

\n\n
test.cpp: In instantiation of \xe2\x80\x98void foo_1(const ArgTypes ...) [with ArgTypes = {const char*, int, const char*, const char*}]\xe2\x80\x99:\ntest.cpp:17:29:   required from here\ntest.cpp:12:16: error: invalid conversion from \xe2\x80\x98int\xe2\x80\x99 to \xe2\x80\x98const char*\xe2\x80\x99 [-fpermissive]\n detail::foo({strings...});\n ~~~~~~~~~~~^~~~~~~~~~~~~~\n
Run Code Online (Sandbox Code Playgroud)\n\n

而基于 SFINAE(liliscent 的实现)会产生:

\n\n
test2.cpp: In function \xe2\x80\x98int main()\xe2\x80\x99:\ntest2.cpp:14:29: error: no matching function for call to \xe2\x80\x98foo(const char [6], const char [6], int)\xe2\x80\x99\n     foo("hello", "world", 42);\n                         ^\ntest2.cpp:7:6: note: candidate: \xe2\x80\x98template<class ... Args, typename std::enable_if<(is_same_v<const char*, Args> && ...), int>::type <anonymous> > void foo(Args ...)\xe2\x80\x99\n void foo(Args... args ){\n  ^~~\ntest2.cpp:7:6: note:   template argument deduction/substitution failed:\ntest2.cpp:6:73: error: no type named \xe2\x80\x98type\xe2\x80\x99 in \xe2\x80\x98struct std::enable_if<false, int>\xe2\x80\x99\n     std::enable_if_t<(std::is_same_v<const char*, Args> && ...), int> = 0>\n
Run Code Online (Sandbox Code Playgroud)\n