27

我有一个函数,它接受一个具有默认值的参数。现在我还希望它采用可变数量的参数并将它们转发给其他函数。具有默认值的函数参数必须放在最后,所以...我可以将该参数放在可变参数包之后,编译器会在调用函数时检测我是否提供它?

(假设包不包含最后一个参数的类型。如果有必要,我们可以假设,因为该类型通常不应该为用户所知,否则无论如何它都被认为是我的界面的错误使用.. ..)

template <class... Args>
void func (Args&&... args, SomeSpecialType num = fromNum(5))
{
}
4

4 回答 4

22

不,包必须放在最后。

但你可以伪造它。您可以检测到包装中的最后一种类型是什么。如果是SomeSpecialType,你可以运行你的函数。如果不是,您可以使用转发和附加SomeSpecialType的参数递归调用自己。fromNum(5)

如果您想花哨的话,可以使用 SFINAE 技术在编译时(即不同的重载)完成此检查。但这可能不值得麻烦,考虑到“运行时”检查将在给定的过载情况下保持不变,因此几乎肯定会被优化,并且不应轻易使用 SFINAE。

这不会给你你想要的签名,但它会给你你想要的行为。您必须在评论中解释预期的签名。

像这样,在你删除错别字等之后:

// extract the last type in a pack.  The last type in a pack with no elements is
// not a type:
template<typename... Ts>
struct last_type {};
template<typename T0>
struct last_type<T0> {
  typedef T0 type;
};
template<typename T0, typename T1, typename... Ts>
struct last_type<T0, T1, Ts...>:last_type<T1, Ts...> {};

// using aliases, because typename spam sucks:
template<typename Ts...>
using LastType = typename last_type<Ts...>::type;
template<bool b, typename T=void>
using EnableIf = typename std::enable_if<b, T>::type;
template<typename T>
using Decay = typename std::decay<T>::type;

// the case where the last argument is SomeSpecialType:
template<
  typename... Args,
  typename=EnableIf<
    std::is_same<
      Decay<LastType<Args...>>,
      SomeSpecialType
    >::value
  >
void func( Args&&... args ) {
  // code
}

// the case where there is no SomeSpecialType last:    
template<
  typename... Args,
  typename=EnableIf<
    !std::is_same<
      typename std::decay<LastType<Args...>>::type,
      SomeSpecialType
    >::value
  >
void func( Args&&... args ) {
  func( std::forward<Args>(args)..., std::move(static_cast<SomeSpecialType>(fromNum(5))) );
}

// the 0-arg case, because both of the above require that there be an actual
// last type:
void func() {
  func( std::move(static_cast<SomeSpecialType>(fromNum(5))) );
}

或类似的东西。

于 2013-02-11T02:48:01.470 回答
10

另一种方法是通过元组传递可变参数。

template <class... Args>
void func (std::tuple<Args...> t, SomeSpecialType num = fromNum(5))
{
  // don't forget to move t when you use it for the last time
}

优点:接口更简单,重载和添加默认值参数非常容易。

std::make_tuple缺点:调用者必须在or调用中手动包装参数std::forward_as_tuple。此外,您可能不得不诉诸std::index_sequence技巧来实现该功能。

于 2015-09-28T07:32:51.143 回答
1

这有点晚了,但在 C++17 中你可以做到这一点,std::tuple总体上会很好。这是对@xavlours 答案的扩展:

template <class... Args>
void func (std::tuple<Args&&...> t, SomeSpecialType num = fromNum(5))
{
    // std::apply from C++17 allows you to iterate over the tuple with ease
    // this just prints them one by one, you want to do other operations i presume
    std::apply([](auto&&... args) {((std::cout << args << '\n'), ...);}, t);
}

然后,制作一个简单的函数来准备它们:

template<typename... Args>
std::tuple<Args&&...> MULTI_ARGS(Args&&... args) {
    return std::tuple<Args&&...>(args...);
}

现在您可以像这样调用该函数:

func(MULTI_ARGS(str1, int1, str2, str3, int3)); // default parameter used
func(MULTI_ARGS(str1, int1, str2));  // default parameter used
func(MULTI_ARGS(str1, int1, str2, str3, int3, otherStuff), fromNum(10)); // custom value instead of default

免责声明:我在设计记录器时遇到了这个问题,并希望有一个默认参数,其中包含std::source_location::current()并且据我所知,这是确保调用者信息准确传递的唯一方法。制作函数包装器将更改source_location信息以表示包装器而不是原始调用者。

于 2021-12-26T07:33:43.027 回答
0

由于 C++17 可以通过使用类模板参数推导用户定义的推导指南来解决此限制。

这对于 C++20 std::source_location尤其有用。

这是 C++17 演示:

#include <iostream>

int defaultValueGenerator()
{
    static int c = 0;
    return ++c;
}

template <typename... Ts>
struct debug
{    
    debug(Ts&&... ts, int c = defaultValueGenerator())
    {
        std::cout << c << " : ";
        ((std::cout << std::forward<Ts>(ts) << " "), ...);
        std::cout << std::endl;
    }
};

template <typename... Ts>
debug(Ts&&...args) -> debug<Ts...>;

void test()
{
    debug();
    debug(9);
    debug<>(9);
}

int main()
{
    debug(5, 'A', 3.14f, "foo");
    test();
    debug("bar", 123, 2.72);
}

现场演示

带有 source_location 的演示(应该从 C++20 开始可用,但对于编译器来说仍然是实验性的)。

于 2022-02-11T15:41:30.737 回答