泛型向量类中的重载解决问题

时间:2015-04-30 11:40:46

标签: c++ templates c++11 generics lambda

我有一个通用的矢量类,我在其中实现了一个接受仿函数的构造函数。这个想法是调用仿函数来初始化向量中的每个元素,仿函数的单个参数是元素索引。

然而,当传递与矢量元素类型不同类型的标量时,这会干扰我的“来自标量”构造函数:

template <typename T, int N>
struct SomeVectorClass
{
    T values[N];

    SomeVectorClass() {}

    SomeVectorClass(T scalar)
    {
        for (int i = 0; i < N; i++) values[i] = scalar;
    }

    template <typename InitFunctionType>
    SomeVectorClass(InitFunctionType initFunction)
    {
        for (int i = 0; i < N; i++) values[i] = initFunction(i);
    }
};

...

SomeVectorClass<int, 2> a([&] (int i) { return i; }); // This works
SomeVectorClass<int, 2> b(123); // This works
SomeVectorClass<int, 2> c(123.f); // This causes error "called object type 'float' is not a function or function pointer"

我对c的期望是编译器自动将123.f转换为123,然后调用“来自标量”构造函数(接受int in这种情况)。

三个问题:

  1. 重载决策是否优先考虑lambda构造函数,因为它不需要强制转换?
  2. 有没有办法让这项工作没有std::enable_if或类似的黑客?
  3. 如果没有,如果std::enable_if是仿函数,我将如何使用InitFunctionType仅启用仿函数构造函数?
  4. 提前感谢您的时间!

    编辑:由于性能原因,忘记提及我不能使用std::function。该课程需要内联友好。

2 个答案:

答案 0 :(得分:2)

构造函数模板是首选,因为它是完全匹配,而T构造函数需要转换。完全匹配是首选。

这正是enable_if存在要解决的问题。您需要从重载决策集中删除构造函数模板,除非它实际上可以使用int调用:

template <typename F,
          typename = typename std::result_of <F(int)>::type>
SomeVectorClass (F func) { }

这样,使用floatchar构建将选择您的T构造函数。这是唯一的候选人!

注意std::result_of不一定是SFINAE友好的(N3462)。以上内容适用于gcc 4.9.2,但不适用于gcc 4.7.2。如果上述内容无法编译,请使用类似

的内容
  

include / c ++ / 4.7.2 / type_traits:1834:9:错误:'std::declval<float>()'不能用作函数

然后你可以将“functor”construtor重写为:

template <typename F,
          typename = decltype(std::declval<F>()(std::declval<int>()))
SomeVectorClass (F func) { }

答案 1 :(得分:0)

这是一个C ++ 11代码墙,其中包含编译器可能没有的一堆C ++ 14特性,以及一些有用的元编程机制:

template<class T>struct tag{using type=T;}; // hold tags
template<class Tag>using type_t=typename Tag::type; // get types from tags
template<class...>struct voider:tag<void>{}; // everything goes to void

template<class...Ts>using void_t=type_t<voider<Ts...>>; // nothing is

template<class...>struct types{using type=types;}; // bundle of types

namespace details {
  // SFINAE friendly result_of:
  template<class Sig, class=void>
  struct invoke;
  template<class F, class...Args>
  struct invoke<F(Args...),
    void_t< decltype( std::declval<F>()(std::declval<Args>()...)) >
  >:tag< decltype( std::declval<F>()(std::declval<Args>()...)) > {};
};
// C++14 style type alias for the above:
template<class Sig>
using invoke_t = type_t<details::invoke<Sig>>;

namespace details {
  // can apply takes a template and some types
  // and is true iff applying the types to the template is valid:
  template<template<class...>class Z, class types, class=void>
  struct can_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, types<Ts...>, void_t<Z<Ts...>>>:
    std::true_type
  {};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z,types<Ts...>>;

// together, this gives us a "can we invoke a signature"
// in one line:
template<class Sig>
using can_invoke=can_apply< invoke_t, Sig >;

// C++14 feature, want it in C++11:
template<bool b, class X=void>
using enable_if_t=type_t<std::enable_if<b,X>>

现在,上述问题很简单:

template<class F, class=enable_if_t< can_invoke<F&(int)>::value >>
SomeVectorClass(F&& initFunction)
{
    for (int i = 0; i < N; i++)
      values[i] = initFunction(i);
}

易于阅读。始终尝试在实用程序标头中隐藏讨厌的decltype内容,不要将其与最终用户代码混合使用。