1

我有一组用作模板参数的类。它们都符合一些非正式的界面(又名概念)

template <typename T>
int func( T& t ) { return t.a() + t.b() + t.c(); }

在这个例子中,假设我用FooorBar作为参数来实例化模板,所以它们必须实现方法a bc.

struct Foo { int a(); int b(); int c(); };
struct Bar { int a(); int b(); int c(); };

现在,我有很多这样的类,我希望有一个函数的默认实现,就其他函数而言。

例如,我想默认c返回和之间的差异a()b()所以我希望我定义a()并自动实现就足够了,b()而无需为所有类复制此​​代码。c()int c() { return a()- b();}

我曾经通过多态性来实现这个结果(通过在具有默认(虚拟)实现的基类中定义a()和作为纯虚函数),但出于性能原因,我放弃了这种机制。b()c()

我想知道是否有推荐的解决方案来使用我的模板参数类获得这种结果(即编写一次默认实现)。

4

4 回答 4

9

我很想从std::begin.

CRTP 很棒,但它要求每个结构都进行自我修改以处理您对 c 的要求。实际上,c 的代码是您的问题,而不是您所提供的数据的问题。

自然,您会想要零开销,CRTP 和这种方法都可以实现。

因此,我们有条件地调用.c().a()+.b()根据它的存在调用。这里有两种方法:

创建一个自由函数c

template<class T, class...Ignored>
decltype(auto) c(T& t, Ignored&&...)

它分派给两个实现:

{
  auto which = has_c_method<T>;
  return details::c(which{}, t);
}

wherehas_c_method是一个 traits bool 类型,用于检测传递的类型是否有.c()方法。(我在下面写一个)。

在命名空间详细信息中:

namespace details{
  template<class T>
  auto c(std::false_type, T&){
    return t.a()-t.b();
  }
  template<class T>
  auto c(std::true_type, T&){
    return t.c();
  }
}

我们很好。c(t)另请注意,如果 s 命名空间中有一个免费的非可变函数t,它将是首选的(就是这样Ignored做的)。

您确实必须编写该特征类,但是许多 SO 答案都涵盖了这一点。

一个比c建议更好的名字。;)

这种设计的优点是不会强迫编写目标类型的人参与操作。您只需访问t.c()t.a()+t.b()根据是否t.c()定义。


现在我们可以从一个更通用的方向来解决这个问题。我们不会创建一个c为我们调度的函数,而是......

我们编写一个编译时分支:

namespace details {
  template<bool>
  struct branch {
    template<class T, class F_true, class F_false>
    std::result_of_t<F_true(T)> operator()( T&&t, F_true&&f, F_false&&){
      return decltype(f)(f)(decltype(t)(t));
    }
  };
  template<>
  struct branch<false> {
    template<class T, class F_true, class F_false>
    std::result_of_t<F_false(T)> branch( T&& t, F_true&&, F_false&&f){
      return decltype(f)(f)(decltype(t)(t));
    }
  };
}
template<template<class...>class Z, class T, class F_true, class F_false>
auto branch( T&& t, F_true&& f_true, F_false&& f_false )
-> decltype( details::branch<Z<T>{}>{}(std::declval<T>(), std::declval<F_true>(), std::declval<F_false>() )
{
  return details::branch<Z<T>{}>{}(decltype(t)(t), decltype(f_true)(f_true), decltype(f_false)(f_false) );
}

没有虚假案例:

template<template<class...>class Z, class T, class F_true>
void branch( T&& t, F_true&& f_true )
{
  branch( std::forward<T>(t), std::forward<F_true>(f_true), [](auto&&){} );
}

利用:

int c = branch<has_c_method>(
  t,
  [&](auto& t){ return t.c(); },
  [&](auto& t){ return t.a()-t.b(); }
);

这使您可以更临时地执行此操作。

branch<template>( arg, if_true, if_false )评估template类型(包括 r/l 值限定)arg。如果结果类型的实例在 constexpr 上下文中返回 true,if_true则运行。如果它在 constexpr 竞赛中返回 false,if_false则运行。

在这两种情况下,arg都将传递给选定的 lambda。

autoC++14 的 lambda 支持一起,这使您可以编写相对简洁的有条件编译的代码。

未运行的 lambda 只是一个未实例化的模板。run lambda 使用 arg 的实例进行实例化。因此,未运行的 lambda 在未选择的情况下不需要包含有效代码。

的类型branch实际上是在两个选项之间静态选择的;他们实际上可以返回不同的类型。没有进行转换。

return 的 if_false-less 重载branchvoid因为我很懒,而且我看不到太多用处。


has_c_method这是用大部分通用代码编写的草图。

namespace details {
  template<template<class...>class Z, class, class...Ts>
  struct can_apply_helper:
    std::false_type
  {};
  template<template<class...>class Z, class...Ts>
  struct can_apply_helper<Z, std::void_t<Z<Ts...>>, Ts...>:
    std::true_type
  {};
}
// is true_type iff Z<Ts...> is valid:
template<template<class...>class Z, class...Ts>
using can_apply = typename details::can_apply_helper<Z, void, Ts...>::type;

// return type of t.c(args...).  Easy to write
// and with the above, makes has_c_method a one-liner:
template<class T, class...Args>
using c_method_result = decltype(std::declval<T>().c(std::declval<Args>()...));

template<class T, class...Args>
using has_c_method = can_apply<c_method_result, T, Args...>;

有一个建议添加一些非常喜欢can_apply的东西std


请注意我对上述的非惯用用法decltype(x)(x)。这等效于std::forward<X>(x)在 whereX是转发引用的上下文中,并且也适用于auto&&参数 lambdas。这意味着“x转换为声明它的类型”。请注意,如果x是值(非引用)类型,它将复制它(这是首选转发的原因,它永远不会这样做):但是,在我的上述任何decltype(x)(x)用途中都不是这种情况。

于 2016-01-14T19:15:16.823 回答
2

如何使用 CRTP 为继承自它的类提供默认实现:

template <typename Child>
class DefaultC
{
public:
    int c() { Child& this_obj = static_cast<Child&>(*this); return this_obj.a()- this_obj.b();}
};

然后:

struct Foo : public DefaultC<Foo> { int a(); int b(); };

(作为一个注释,如果你的函数是非变异的,请将它们标记为 const)

于 2016-01-14T18:55:40.347 回答
2

我会先尝试 CRTP:

template < typename Derived >
struct subtract
{
    int c() const
    {
        auto this_ = static_cast<Derived const*>(this);
        return this_->a() - this_->b();
    }
};

struct whatnot : subtract<whatnot>
{
    int a() const { return 42; }
    int b() const { return 666; }
};
于 2016-01-14T18:56:34.060 回答
2

一个版本,灵感来自 Kerrek 关于评论的版本,但使用std::true_typestd::false_type

#include <iostream>
#include <type_traits>

struct Foo {
    int a() { return 10; }
    int b() { return 20; }
    int c() { return 30; }
};

struct Bar {
    int a() { return 8; }
    int b() { return 3; }
};

template<typename T, typename = void>
struct has_c : std::false_type {
    static int call(T t) { return t.a() - t.b(); }
};

template<typename T>
struct has_c<T, decltype(std::declval<T>().c(), void())> : std::true_type {
    static int call(T t) { return t.c(); }    
};

template <typename T>
int f(T&& t) {
    return has_c<T>::call(std::forward<T>(t));
}

int main()
{
    Foo foo;
    Bar bar;

    std::cout << f(foo) << "\n";
    std::cout << f(bar) << "\n";

    return 0;
}

住在科利鲁

于 2016-01-14T19:18:29.607 回答