简体   繁体   English

C ++模板参数默认功能实现

[英]C++ template parameter default function implementation

I have a set of classes that are used as parameters to templates. 我有一组用作模板参数的类。 They all conform to some informal interface ( aka a concept ) 它们都符合某种非正式的界面(又称概念)

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

In this example, le'ts say I instantiate templates with Foo or Bar as parameters, so they must implement methods a b and c . 在此示例中,我说我以FooBar作为参数实例化模板,因此它们必须实现方法a bc

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

Now, I have a lot of these classes, and I would like to have a default implementation of one of the functions in terms of the others. 现在,我有很多这样的类,并且我想对其中一个函数进行默认实现。

For example, I want c to return the difference between a() and b() by default. 例如,我希望c默认返回a()b()之间的差。 So I wish that it would be enough that I define a() and b() and c() would automatically implemented as int c() { return a()- b();} without having to copy this code for all classes. 因此,我希望定义a()并将b()c()自动实现为int c() { return a()- b();}就足够了,而不必为所有类复制此​​代码。

I used to achieve this result with polymorphism ( by defining a() and b() as pure virtual functions in a base class with a default (virtual) implementation of c() ), but I moved away from this mechanism for performance reasons. 我曾经通过多态性来实现此结果(通过将a()b()定义a()具有c()的默认(虚拟)实现的基类中的纯虚函数),但是出于性能原因,我放弃了这种机制。

I would like to know if there is a recommended solution to attain this kind of result (ie write the default implementation once) with my template parameter classes. 我想知道是否存在一种推荐的解决方案,可以通过我的模板参数类来获得这种结果(即编写一次默认实现)。

I would be tempted to steal a page from std::begin . 我很想从std::begin窃取页面。

CRTP is great, but it requires every structure modify itself to handle your requirement of having a c. CRTP很棒,但是它要求每个结构都自行修改以处理您拥有c的要求。 And really, the code for c is your problem, not the problem of the data you are being fed. 实际上,c的代码是您的问题,而不是您正在馈入的数据的问题。

Naturally you'll want zero-overhead, which both CRTP and this approach pulls off. 自然,您将需要零开销,CRTP和这种方法都可以实现。

So instead, we conditionally call .c() or we call .a()+.b() depending on its existence. 因此,取而代之的是,根据其存在条件,有条件地调用.c().a()+.b() Here are two approaches: 这是两种方法:

Create a free function c : 创建一个自由函数c

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

It dispatches to two implementations: 它分派给两个实现:

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

Where has_c_method is a traits bool type that detects if the type passed has a .c() method. has_c_method是特征布尔类型,它检测传递的类型是否具有.c()方法。 (I write one below). (我在下面写一个)。

In namespace details: 在名称空间详细信息中:

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();
  }
}

and we are good. 而且我们很好。 Also note that if there is a free non-variadic function c(t) in t s namespace, it will be preferred (that is what Ignored does). 还要注意,如果t s命名空间中有一个自由的非可变函数c(t) ,则它将是首选函数(这是被Ignored功能)。

You do have to write that traits class, but many SO answers cover that. 您必须编写该traits类,但是许多SO答案都涵盖了该类。

A better name than c is advised. 建议使用比c更好的名称。 ;) ;)

This design has the advantage of not forcing people writing your target types to get in on the action. 这种设计的优点是不会强迫人们编写您的目标类型来进行操作。 You simply access either tc() or ta()+tb() depending on if tc() is defined. 您只需访问tc()ta()+tb()具体取决于是否定义了tc()


Now we could approach this from an even more generic direction. 现在,我们可以从一个更通用的方向来解决这个问题。 We don't create a c function that dispatches for us, instead... 我们不创建为我们调度的c函数,而是...

We write a compile-time branch: 我们编写一个编译时分支:

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) );
}

no false case: 没有错误的情况:

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&&){} );
}

use: 采用:

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

which lets you do this a bit more ad-hoc. 这使您可以更临时地执行此操作。

branch<template>( arg, if_true, if_false ) evaluates template on the type (including r/l value qualification of) arg . branch<template>( arg, if_true, if_false )评估template上的类型(包括R / L值的资格) arg If an instance of the type resulting returns true within a constexpr context, if_true is run. 如果结果类型的实例在constexpr上下文中返回true,则运行if_true If it returns false within a constexpr contest, if_false is run. 如果在constexpr竞赛中返回false,则运行if_false

In both cases, the arg is passed to the selected lambda. 在这两种情况下, arg都传递给选定的lambda。

Together with auto lambda support from C++14, this lets you write code that is conditionally compiled with relative brevity. 加上C ++ 14的auto lambda支持,这使您可以编写相对简短的有条件编译的代码。

The unrun lambda is just an uninstantiated template. 未运行的lambda只是一个未实例化的模板。 The run lambda is instantiated with an instance of arg. 运行lambda用arg的实例实例化。 So the unrun lambda need not contain valid code for the case where it isn't selected. 因此,未选中的lambda不需要包含有效的代码。

The type of the branch is actually statically selected between the two options; 实际上, branch的类型是在两个选项之间静态选择的。 they can actually return different types. 他们实际上可以返回不同的类型。 No conversion is done. 没有转换完成。

The if_false-less overload of branch returns void , as I am lazy and I don't see much use. branch的if_false-less-less重载将返回void ,因为我很懒,我看不出有什么用。


Here is a sketch of has_c_method written with mostly generic code. 这是用大多数通用代码编写的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...>;

there is a proposal to add something very much like can_apply to std . 有一个建议将非常类似于can_apply东西添加到std


Note my non-idiomatic use of decltype(x)(x) above. 请注意我上面的decltype(x)(x)非惯用用法。 This is equivalent to std::forward<X>(x) in a context where X is a forwarding reference, and also works within auto&& parameter lambdas. X是转发引用的上下文中,这等效于std::forward<X>(x) ,并且还可以在auto&&参数lambda中使用。 It means "cast x to the type it was declared with". 它的意思是“将x转换为声明的类型”。 Note that if x is a value (non-reference) type, it will duplicate it (which is a reason to prefer forward, which never does that): this, however, is not the case in any of my above decltype(x)(x) uses. 请注意,如果x是一个值(非引用)类型,它将对其进行复制 (这是偏向于转发的原因,永远不会这样做):但是,在我上面的任何decltype(x)(x) ,情况并非如此decltype(x)(x)使用。

What about using CRTP to provide the default implementation to classes that inherit from it: 如何使用CRTP为从其继承的类提供默认实现:

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

Then: 然后:

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

(and as a note if your functions are non-mutating please mark them const) (并且请注意,如果您的函数是不变的,请将其标记为const)

I would first try CRTP: 我首先尝试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; }
};

A version, inspired by Kerrek's one on comments, but using std::true_type and std::false_type 受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;
}

Live on Coliru 住在科利鲁

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM