简体   繁体   中英

decltype does not deduce const members for const objects

#include <type_traits>
#include <functional>

struct Chains
{};

struct Stages
{

    Chains mutating_chains;

    Chains sideffect_chains;

    Chains write_chains;

    void forall_chains(const std::function<void(Chains & chain)> & fun)
    {
        forall_chains(*this, fun);
    }

    void forall_chains(
        const std::function<void(const Chains & chain)> & fun) const
    {
        forall_chains(*this, fun);
    }

    template <typename Self>
    static void forall_chains(
        Self & self,
        const std::function<void(decltype(self.mutating_chains) & chain)> & fun)
    {
        fun(self.mutating_chains);
        fun(self.sideffect_chains);
        fun(self.write_chains);
    }
};

There is obviously something that I can't understand with decltype . Because according to the error message that the compiler throws, Self is deduced as const Stages, so Why self.member is not deduced as const member? Also how to make it work correctly, deduce const members for const objects? I added parenthesis to the expression decltype((self.mutating_chains)) and that passed compilation but I'm not sure if that is the correct thing to do.

f.cpp: In instantiation of ‘static void Stages::forall_chains(Self&, const std::function<void(decltype (self.mutating_chains)&)>&) [with Self = const Stages; decltype (self.mutating_chains) = Chains]’:
f.cpp:150:33:   required from here
f.cpp:158:33: error: no match for call to ‘(const std::function<void(Chains&)>) (const Chains&)’
         fun(self.mutating_chains);

I added parenthesis to the expression decltype((self.mutating_chains)) and that passed compilation but I'm not sure if that is the correct thing to do.

Yes, that is the correct thing to do in this case. In short, decltype(x) gives you the declared type of x , which does not depend on the value category of the expression.

For an lvalue expression x of type T , decltype((x)) instead yields T& , which in your case gets the const qualifier applied correctly.

You can find a more formal (and accurate) explanation on the cppreference page for decltype(...) .


By the way, please consider passing your callback via a template argument instead of std::function . The latter is not a zero-cost abstraction - it's a heavyweight wrapper that uses type erasure whose usage should be minimized.

template <typename Self, typename F>
static void forall_chains(Self& self,  F&& fun){ /* ... */ }

I wrote an article about the subject: passing functions to functions .

Yes, indeed. decltype has a special case for decltype(self.mutating_chains) (emphasis mine):

If the argument is an unparenthesized id-expression or an unparenthesized class member access expression , then decltype yields the type of the entity named by this expression.

So you get the actual type with which self.mutating_chains has been declared, that is Chains .

When you add parentheses, you fall back into the general case, which actually evaluates the type of the expression, which in the case of a const Self is const Chains & as expected:

If the argument is any other expression of type T , and
a) [...]
b) if the value category of expression is lvalue, then decltype yields T& ;
c) [...]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM