Search code examples
c++visual-c++templatesargument-passingboost-function

Error C2228 when constructing boost::function object in constructor argument list


The code below does not compile in Visual C++ 2005.

class SomeClass {
public: boost::function<void()> func;
        SomeClass(boost::function<void()> &func): func(func) { }
};

void someFunc() {
    std::cout << "someFunc" << std::endl;
}

int main() {
    SomeClass sc(boost::function<void()>(&someFunc));
    sc.func(); // error C2228: left of '.func' must have class/struct/union
    return 0;
}

If I put parentheses around the argument to the SomeClass constructor or constructs the boost::function object outside the argument list it compiles fine.

    SomeClass sc((boost::function<void()>(&someFunc)));
    // or
    boost::function<void()> f(&someFunc);
    SomeClass sc(f);

What is the problem with the previous code?


Solution

  • It's a function declaration for a function taking a reference to a boost:function <void()> and returning a SomeClass. You can memorize the following rule, which turns out to apply to many other such disambiguation cases. You can find descriptions of these cases in section 8.2 of the C++ Standard.

    Any construct that could possibly be a declaration will be taken as a declaration

    That means, the following will be taken as a parameter declaration, with superfluous parentheses

    boost::function<void()>(&someFunc)
    

    If you remove the parentheses, this will become clear

    boost::function<void()> &someFunc
    

    And thus, the whole declaration will not anymore declare an object, but a function

    SomeClass sc(boost::function<void()> &someFunc);
    

    To fix it, use the cast-notation

    SomeClass sc((boost::function<void()>)&someFunc);
    

    Or put parentheses around the whole expression, like you did.

    Here is the Standard in all its glory from 8.2:

    The ambiguity arising from the similarity between a function-style cast and a declaration mentioned in 6.8 can also occur in the context of a declaration. In that context, the choice is between a function declaration with a redundant set of parentheses around a parameter name and an object declaration with a function-style cast as the initializer. Just as for the ambiguities mentioned in 6.8, the resolution is to consider any construct that could possibly be a declaration a declaration. [Note: a declaration can be explicitly disambiguated by a nonfunction-style cast, by a = to indicate initialization or by removing the redundant parentheses around the parameter name. ]

    Note that for controlling precedence, you are allowed to introduce parentheses just about anywhere, like in the following

    int (((((((a))))))) = 3;
    int (*(pa)) = &a;