C++模板传递方法名,推断所有重载

huangapple go评论66阅读模式
英文:

C++ template pass method name, deduce all overload

问题

我有简单的类

template <typename T>
using ConversionFunction = T(*)(T val);

static int Foo1(int x)
{
	return x * x;
}

static double Foo1(double x)
{
	return x * x + 1;
}


struct Foo
{
	ConversionFunction<double> d;
	ConversionFunction<int> i;
	ConversionFunction<float> f;
	
	template <typename T>
	void assign(){
		//??
	}
};

能够像这样写吗:

Foo f;
f.assign<Foo1>();

并且assign方法会自动获取所有现有的具有Foo1名称的方法的指针吗?类似这样:

template <typename T>
void assign(){    		
  d = if exist(T for double) ? &(T for double) : nullptr;
  i = if exist(T for int) ? &(T for int) : nullptr;
  f = if exist(T for float) ? &(T for float) : nullptr;
}
英文:

I have simple class

template <typename T>
using ConversionFunction = T(*)(T val);

static int Foo1(int x)
{
	return x * x;
}

static double Foo1(double x)
{
	return x * x + 1;
}


struct Foo
{
	ConversionFunction<double> d;
	ConversionFunction<int> i;
	ConversionFunction<float> f;
	
	template <typename T>
	void assign(){
		//??
	}
};

Is it possible to write something like this:

Foo f;
f.assign<Foo1>();

and assign method will automatically obtain pointers to all existing methods with the Foo1 name? Something like this:

template <typename T>
void assign(){    		
  d = if exist(T for double) ? &(T for double) : nullptr;
  i = if exist(T for int) ? &(T for int) : nullptr;
  f = if exist(T for float) ? &(T for float) : nullptr;
}

答案1

得分: 2

你不能仅通过名称传递重载集合。

替代方法是将其包装在类/lambda中:

template <typename T>
struct to_function_pointer
{
    template <typename U>
    operator ConversionFunction<U> () const { return [](U x){ return T{}(x); }; }
};

const auto foo_caller = to_function_pointer<decltype([](auto x) -> decltype(Foo1(x)){ return Foo1(x); })>();

演示

英文:

You can't just use name to pass overload set.

Alternative is to wrap it inside a class/lambda:

template &lt;typename T&gt;
struct to_function_pointer
{
    template &lt;typename U&gt;
    operator ConversionFunction&lt;U&gt; () const { return [](U x){ return T{}(x); }; }
};

const auto foo_caller = to_function_pointer&lt;decltype([](auto x)-&gt; decltype(Foo1(x)){ return Foo1(x); })&gt;();

Demo

答案2

得分: 2

名字在C++类型系统中无法传递。这是许多问题的主要痛点,但这就是现状。你可以传递值,或者在模板的情况下:类型和其他模板也可以。但在任何上下文中,“名字”必须解析为一个值或类型,然后才能在其它地方“传递”。

唯一的(有点)例外是预处理器,因为它操作令牌,你可以利用它从名字生成新的值和类型。如果这不让你反感,你可以在C++20及以后创建这个基础设施。

该宏实际上只是一种将重载集的名称转换为新函数对象的方法(因此称为“LIFT”命名法)。一旦你有了它,你可以将它传递到函数(和函数模板)中,并检查它的属性是否满足SFINAE-friendly的要求。

通用lambda函数接受传达类型的标签,添加了可以将该类型的函数指针从“name”分配给它的要求(因此return也是有效的),以使其符合SFINAE-friendly的条件,就是这样。assign的主体简单地使用了一堆requires表达式来检查这个SFINAE-friendly的函数对象是否可以被调用,如果可以,就调用它。

上面的大部分内容可以在较早的标准(包括C++14)中模拟,尽管实现会变得越来越不愉快。

英文:

Names cannot be passed around in the C++ type system. It's a major source of pain in many problems, but it's just the way it is. You can pass values around, or in the case of templates: types and other templates as well. But names must be resolved to a value or type before they are "passed on" in any context.

The only (sort of) exception to that is the pre-processor, since it operates on tokens, you can leverage it to generate new values and type from names. If that is not something that turns your stomach, you could create this plumbing in C++20 and onward

#include &lt;type_traits&gt;
#include &lt;cassert&gt;

#define LIFT_ADDRESS(name)                       \
[]&lt;typename T&gt;(std::type_identity&lt;T&gt;) -&gt; T(*)(T) \
requires requires(T(*ptr)(T)) { ptr = name; } {  \
    return name;                                 \
}

template &lt;typename T&gt;
using ConversionFunction = T(*)(T);

static int Foo1(int x)
{
    return x * x;
}

static double Foo1(double x)
{
    return x * x + 1;
}


struct Foo
{
    ConversionFunction&lt;double&gt; d{};
    ConversionFunction&lt;int&gt; i{};
    ConversionFunction&lt;float&gt; f{};
    
    template &lt;class Lifted&gt;
    void assign(Lifted l) {
        if constexpr(requires { l(std::type_identity&lt;double&gt;{}); })
            d = l(std::type_identity&lt;double&gt;{});
        if constexpr(requires { l(std::type_identity&lt;int&gt;{}); })
            i = l(std::type_identity&lt;int&gt;{});
        if constexpr(requires { l(std::type_identity&lt;float&gt;{}); })
            f = l(std::type_identity&lt;float&gt;{});
    }
};

int main() {
    Foo f;
    f.assign(LIFT_ADDRESS(Foo1));

    assert(f.d);
    assert(f.i);
    assert(!f.f);
}

The macro is really just a means to turning the names of overload sets into new function object (hence the "LIFT" nomenclature). Once you have that, you can pass it around into functions (and function templates), as well as examining it for its properties in a requires expression (assuming it's sfiane-friendly).

The generic lambda simply accepts a tag that conveys a type, adds a requirement that a function pointer of said type can be assigned to from "name" (so the return is valid too) to be sfinae-friendly, and that's it. The body of assign simply uses a bunch of requires-expressions to check if this sifnae-friendly functor can be invoked, and if it does, it invokes it.

Much of the above can be emulated in earlier standards down to C++14 (the generic lambda is the bare minimum I believe), though the implementations become increasingly less pleasant.

huangapple
  • 本文由 发表于 2023年4月10日 23:15:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/75978320.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定