Saturday 9 July 2011

Automatic type deduction

I think that DRY is a very important software engineering principle. It is, however, relatively amazing how often our basic programming systems violate it.

class f {
    std::string func();
}
std::string f::func() {
    return std::string("");
}


Count the violations. We said that func returns a std::string twice and implied it at least once, and we also stated that f has a function called func twice.

This is a little resolved by multiple-pass compilation.

class f {
    std::string func() {
        return std::string("");
    }
}


Now we have an improvement- we only told the compiler about f and func once, but we've still doubled up on the return type. As such, I think that automatic type deduction should be the default pretty much everywhere, so for stack-based variables, for inferrable return types (for some definition of inferrable), for argument types. This would be similar to the polymorphic lambdas proposal for C++0x that didn't make it. This can make for some interesting, fluent code when dealing with value types.

template<typename lhs_type, typename rhs_type> add(lhs_type&& lhs, rhs_type&& rhs)
-> decltype(std::forward<lhs_type>(lhs) + std::forward<rhs_type>(rhs)) {
    return std::forward<lhs_type>(lhs) + std::forward<rhs_type>(rhs);
}


versus

add(lhs, rhs) {
    return lhs + rhs;
}


Damn, that'd be a breath of fresh air. Of course, the original add will do better when dealing with things that are already lvalues, as the second performs unnecessary copying. So I need to brush up on my lvalue and rvalue references. I want to change auto to be auto&& instead, and I also want forward to not need an explicit argument anymore. That would mean that I could have

add(lhs, rhs) {
    return forward(lhs) + forward(rhs);
}


But, apparently, I still don't understand rvalue references properly. Amazing that anyone can deal with them beyond calling the Standard library to handle it. I've also been thinking of a kind of last-reference rule, where the last reference to a variable in a function is implicitly an rvalue reference. Not only would that substantially shorten some shorter functions, but in some cases notably involving lambdas which take by value, then it could  result in moving rather than copying. The trouble with this is when you start involving pointers... as usual. It would be so nice if I could just ban them outright. Unfortunately, re-bindable aliases are probably a necessary fact of life. Probably.

I might still be able to get away with

add(auto&& lhs, auto&& rhs) {
    return forward(decltype(lhs))(lhs) + forward(decltype(rhs))(rhs);
}

Edit: I could, of course, just make auto&& the default for function arguments, and auto for returns, local variables, member variables ... :) I'd still have to deal with the problem of forward, though, that really needs cleaning up. I could add a language feature, called forward, that does the same job that doesn't need an explicit type argument if I can't come up with a library feature.

add(lhs, rhs) {
    return forward(lhs) + forward(rhs);
}


It's not quite Python or Lua, but it's pretty damn close.

No comments:

Post a Comment