boost::math::tools::promote_args
Rustと同様のことを達成できますか?慣用的なC ++ 11タイプのプロモーションも参照してください。
具体的には、引数に基づいて関数またはトレイトメソッドの戻り値の型を計算し、戻り値の型が引数の1つと同じ型であることを確認することは可能ですか?
次の場合を考えてみましょう。私には2つの構造体があります:
#[derive(Debug, Clone, Copy)]
struct MySimpleType(f64);
#[derive(Debug, Clone, Copy)]
struct MyComplexType(f64, f64);
特性MySimpleType
をMyComplexType
介してどこに昇格することができますFrom
。
impl From<MySimpleType> for MyComplexType {
fn from(src: MySimpleType) -> MyComplexType {
let MySimpleType(x1) = src;
MyComplexType(x1, 0.0)
}
}
私はタイプの二つの引数を取る関数書きたいMySimpleType
かをMyComplexType
し、型の値を返すMySimpleType
すべての引数がのように入力された場合MySimpleType
、それ以外の機能は、型の値を返す必要がありますMyComplexType
。Add<Output=Self>
両方のタイプに実装したとすると、次のようなことができます。
trait Foo<S, T> {
fn foo(s: S, t: T) -> Self;
}
impl<S, T, O> Foo<S, T> for O
where O: From<S> + From<T> + Add<Output = Self>
{
fn foo(s: S, t: T) -> Self {
let s: O = From::from(s);
let t: O = From::from(t);
s + t
}
}
しかし、その後、コンパイラはそれが分かっていないO
のいずれかであるべきS
か、T
と私はほとんどのメソッド呼び出しに注釈を付ける必要があります。
私の2番目の試みは、わずかに異なる特性を使用して、2つの実装を作成することです。
trait Foo<S, T> {
fn foo(s: S, t: T) -> Self;
}
impl Foo<MySimpleType, MySimpleType> for MySimpleType {
fn foo(s: MySimpleType, t: MySimpleType) -> Self {
s + t
}
}
impl<S, T> Foo<S, T> for MyComplexType
where MyComplexType: From<S> + From<T>
{
fn foo(s: S, t: T) -> Self {
let s: MyComplexType = From::from(s);
let t: MyComplexType = From::from(t);
s + t
}
}
しかし、繰り返しになりますが、コンパイラはの戻り値の型を把握できません。
Foo::foo(MySimpleType(1.0), MySimpleType(1.0))
3番目の試行は、に似たものstd::ops::{Add, Mul, ...}
です。関連する型を使用し、引数型の可能な組み合わせごとに特定の実装を記述します
trait Foo<T> {
type Output;
fn foo(self, t: T) -> Self::Output;
}
impl<T: Add<Output=T>> Foo<T> for T {
type Output = Self;
fn foo(self, t: T) -> Self::Output {
self + t
}
}
impl Foo<MySimpleType> for MyComplexType {
type Output = Self;
fn foo(self, t: MySimpleType) -> Self::Output {
let t: Self = From::from(t);
self + t
}
}
impl Foo<MyComplexType> for MySimpleType {
type Output = MyComplexType;
fn foo(self, t: MyComplexType) -> Self::Output {
let s: MyComplexType = From::from(self);
s + t
}
}
これは、n
引数を持つ関数が必要になるまでは最善の解決策のようです。それなら、2^n - n + 1
impl
ステートメントを書かなければならないからです。もちろん、2つ以上のタイプを検討すると、これはさらに悪化します。
===
編集:
私のコードでは、複数のネストされた関数呼び出しがあり、単純型の関数の評価は複雑型の場合は安価で高価であるため、不要な型の昇格は避けたいと思います。@MatthieuMを使用する。の提案された解決策では、これは達成されていません。次の例を検討してください
#![feature(core_intrinsics)]
use std::ops::Add;
trait Promote<Target> {
fn promote(self) -> Target;
}
impl<T> Promote<T> for T {
fn promote(self) -> T {
self
}
}
impl Promote<u64> for u32 {
fn promote(self) -> u64 {
self as u64
}
}
fn foo<Result, Left, Right>(left: Left, right: Right) -> Result
where Left: Promote<Result>,
Right: Promote<Result>,
Result: Add<Output = Result>
{
println!("============\nFoo called");
println!("Left: {}", unsafe { std::intrinsics::type_name::<Left>() });
println!("Right: {}",
unsafe { std::intrinsics::type_name::<Right>() });
println!("Result: {}",
unsafe { std::intrinsics::type_name::<Result>() });
left.promote() + right.promote()
}
fn bar<Result, Left, Right>(left: Left, right: Right) -> Result
where Left: Promote<Result>,
Right: Promote<Result>,
Result: Add<Output = Result>
{
left.promote() + right.promote()
}
fn baz<Result, A, B, C, D>(a: A, b: B, c: C, d: D) -> Result
where A: Promote<Result>,
B: Promote<Result>,
C: Promote<Result>,
D: Promote<Result>,
Result: Add<Output = Result>
{
let lhs = foo(a, b).promote();
let rhs = bar(c, d).promote();
lhs + rhs
}
fn main() {
let one = baz(1u32, 1u32, 1u64, 1u32);
println!("{}", one);
}
プロモーションを実装する最も簡単な方法は、Promote
トレイトを作成することだと思います。
trait Promote<Target> {
fn promote(self) -> Target;
}
impl<T> Promote<T> for T {
fn promote(self) -> T { self }
}
注:すべてのタイプを自分自身にプロモートできるため、包括的な実装を提供します。
単一のタイプを複数のタイプにプロモートできるため、関連するタイプを使用することはここではオプションではありません。したがって、通常の型パラメーターを使用するだけです。
これを使用した簡単な例は次のとおりです。
impl Promote<u64> for u32 {
fn promote(self) -> u64 { self as u64 }
}
fn add<Result, Left, Right>(left: Left, right: Right) -> Result
where
Left: Promote<Result>,
Right: Promote<Result>,
Result: Add<Output = Result>
{
left.promote() + right.promote()
}
fn main() {
let one: u32 = add(1u32, 1u32);
let two: u64 = add(1u32, 2u64);
let three: u64 = add(2u64, 1u32);
let four: u64 = add(2u64, 2u64);
println!("{} {} {} {}", one, two, three, four);
}
唯一の問題は、2つのu32
引数の場合、結果タイプを指定する必要があることです。指定しないと、コンパイラーはPromote
使用可能な実装を選択できません:Promote<u32>
またはPromote<u64>
。
これが実際の問題であるかどうかはわかりませんが、ある時点で型推論を固定するための具体的な型が必要になるためです。例えば:
fn main() {
let v = vec![add(1u32, 1u32), add(1u32, 2u64)];
println!("{:?}", v);
}
型ヒントなしでコンパイルできます。これadd(1u32, 2u64)
は、のみであるu64
ため、aVec
は同種のコレクションであるadd(1u32, 1u32)
ため、u64
ここを返す必要があります。
ただし、経験したように、型推論で処理できる範囲を超えて結果を指示する機能が必要になる場合があります。それは大丈夫です、あなたはそれのために別の特性が必要です:
trait PromoteTarget {
type Output;
}
impl<T> PromoteTarget for (T, T) {
type Output = T;
}
そして、少しの実装:
impl PromoteTarget for (u32, u64) {
type Output = u64;
}
impl PromoteTarget for (u64, u32) {
type Output = u64;
}
これでbaz
、すべての中間タイプを正しく説明するように署名を書き直すことができます。残念ながら、where
句にエイリアスを導入する方法がわからないので、自分をしっかりと支えてください。
fn baz<Result, A, B, C, D>(a: A, b: B, c: C, d: D) -> Result
where
A: Promote<<(A, B) as PromoteTarget>::Output>,
B: Promote<<(A, B) as PromoteTarget>::Output>,
C: Promote<<(C, D) as PromoteTarget>::Output>,
D: Promote<<(C, D) as PromoteTarget>::Output>,
(A, B): PromoteTarget,
(C, D): PromoteTarget,
<(A, B) as PromoteTarget>::Output: Promote<Result> + Add<Output = <(A, B) as PromoteTarget>::Output>,
<(C, D) as PromoteTarget>::Output: Promote<Result> + Add<Output = <(C, D) as PromoteTarget>::Output>,
Result: Add<Output = Result>
{
let lhs = foo(a, b).promote();
let rhs = bar(c, d).promote();
lhs + rhs
}
ここで遊び場にリンクして、結果を確認できます。
============ Foo called Left: u32 Right: u32 Result: u32 4
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加