Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Ambiguous associated type" when returning a generic lambda

I have this code (contrived and purely experimental)

fn math_op(op: &str) -> Option<Box<Fn(i32, i32) -> i32>> {
    let ret: Option<Box<Fn(i32, i32) -> i32>> = match op {
        "+" => Some(Box::new(|a: i32, b: i32| -> i32 { a + b } )),
        "-" => Some(Box::new(|a: i32, b: i32| -> i32 { a - b } )),
        "*" => Some(Box::new(|a: i32, b: i32| -> i32 { a * b } )),
        "/" => Some(Box::new(|a: i32, b: i32| -> i32 { a / b } )),
        _ => None,
    };

    ret
}

It returns a function/lambda that takes two operands and returns a result (in this case the addition, subtraction, divide and multiply operators)

Which can be called like so:

let add: Option<Box<Fn(i32, i32) -> i32>> = math_op("+");
println!("Add {}", add.unwrap()(10, 2));

I would really like to make a generic version of this, so far I have...

fn math_op_gen<T>(op: &str) -> Option<Box<Fn(T, T) -> T::Output>> 
    where T: std::ops::Add + std::ops::Sub + std::ops::Mul + std::ops::Div {
        let ret: Option<Box<Fn(T, T) -> T::Output>> = match op {
            "+" => Some(Box::new(|a, b| { a + b } )),
            "-" => Some(Box::new(|a, b| { a - b } )),
            "*" => Some(Box::new(|a, b| { a * b } )),
            "/" => Some(Box::new(|a, b| { a / b } )),
            _ => None,
        };

        ret
}

But when I build I get these errors:

error: ambiguous associated type `Output` in bounds of `T` [E0221]
note: associated type `T` could derive from `core::ops::Div`
note: associated type `T` could derive from `core::ops::Mul`
note: associated type `T` could derive from `core::ops::Sub`
note: associated type `T` could derive from `core::ops::Add`

I understand this is because the compiler cannot determine what type T::Output is from the various traits I have implemented. Is there another way of writing this to get it to work?

like image 868
Tom Avatar asked Oct 18 '15 00:10

Tom


People also ask

What is generic Lambda in C++ with example?

C++ Generic lambdas. Example. c++14. Lambda functions can take arguments of arbitrary types. This allows a lambda to be more generic: auto twice = [](auto x){ return x+x; }; int i = twice(2); // i == 4 std::string s = twice("hello"); // s == "hellohello".

What is the syntax for Lambdas?

The syntax for lambdas is one of the weirder things in C++, and takes a bit of getting used to. Lambdas take the form: The capture clause and parameters can both be empty if they are not needed. The return type is optional, and if omitted, auto will be assumed (thus using type inference used to determine the return type).

What happens if return type inference is used in lambda expressions?

If return type inference is used, all return statements in the lambda must return the same type (otherwise the compiler won’t know which one to prefer). This produces a compile error because the return type of the first return statement (int) doesn’t match the return type of the second return statement (double).

Why doesn’t my Lambda have a capture clause?

The lambda has no capture clause (we’ll explain what a capture clause is in the next lesson) because it doesn’t need one. And we’ve omitted the trailing return type in the lambda (for conciseness), but since operator!= returns a bool, our lambda will return a bool too. In the above example, we defined a lambda right where it was needed.


1 Answers

You need the output types of Add, Sub, Mul and Div to be the same. You can enforce this by adding another type parameter and constraining each trait's Output to be this type parameter.

fn math_op_gen<T, R>(op: &str) -> Option<Box<Fn(T, T) -> R>> 
    where T: std::ops::Add<Output=R> +
             std::ops::Sub<Output=R> +
             std::ops::Mul<Output=R> +
             std::ops::Div<Output=R> {
        let ret: Option<Box<Fn(T, T) -> R>> = match op {
            "+" => Some(Box::new(|a, b| { a + b } )),
            "-" => Some(Box::new(|a, b| { a - b } )),
            "*" => Some(Box::new(|a, b| { a * b } )),
            "/" => Some(Box::new(|a, b| { a / b } )),
            _ => None,
        };

        ret
}
like image 187
Francis Gagné Avatar answered Sep 22 '22 21:09

Francis Gagné