std::forward and operator()

3

我一直在研究为我的C++项目编写一个static_if,然后我偶然发现了下面这段代码:

#include <iostream>
using namespace std;

namespace static_if_detail {

struct identity {
    template<typename T>
    T operator()(T&& x) const {
        return std::forward<T>(x);
    }
};

template<bool Cond>
struct statement {
    template<typename F>
    void then(const F& f){
        f(identity());
    }

    template<typename F>
    void else_(const F&){}
};

template<>
struct statement<false> {
    template<typename F>
    void then(const F&){}

    template<typename F>
    void else_(const F& f){
        f(identity());
    }
};

} //end of namespace static_if_detail

template<bool Cond, typename F>
static_if_detail::statement<Cond> static_if(F const& f){
    static_if_detail::statement<Cond> if_;
    if_.then(f);
    return if_;
}

template<typename T>
void decrement_kindof(T& value){
    static_if<std::is_same<std::string, T>::value>([&](auto f){
        f(value).pop_back();
    }).else_([&](auto f){
        --f(value);
    });
}


int main() {
    // your code goes here
    std::string myString{"Hello world"};
    decrement_kindof(myString);
    std::cout << myString << std::endl;
    return 0;
}

对我来说,这一切都很有意义,除了一个问题:在struct identity中重载的运算符()。它接受类型为T的rhs(右值)作为参数x,很酷。但是当调用identity时,实际上没有传入任何参数。

template<typename F>
void then(const F& f){
    f(identity());
}

上面的代码中,f 调用了 identity 函数,但是没有向其传递任何参数。然而,identity 函数返回被转发的参数(在我的例子中是 std::string),并弹出字符串的最后一个字符。 当 identity 函数本身没有传入任何要转发的参数时,它如何返回被转发的参数呢?

1
在我看来,f 看起来是一个函数,并且它被调用时带有一个类型为 identity 的单个参数。换句话说,identity() 构造了一个 identity 类型的实例,该实例被传递给 f 函数。 - James Adkison
1
这是构建和调用类似对象的示例:[http://ideone.com/p8SlI9] - James Adkison
3个回答

5

f 不会调用 identity - f 被一个 identity 的实例调用。这里简单解释一下两种情况:

static_if<std::is_same<std::string, T>::value>([&](auto f){
    f(value).pop_back();
}).else_([&](auto f){
    --f(value);
});

如果 Tstd::string,那么我们将实例化一个 statement<true>,它的 then() 将使用 identity 的实例调用传入的函数。第一个 lambda 函数的参数 f 的类型将是 identity,这意味着 f(value) 实际上只是 value,然后我们执行 value.pop_back()
如果 T 不是 std::string,那么我们将实例化一个 statement<false>,它的 then() 什么都不做,并且它的 else_() 将使用 identity 的实例调用该 lambda 函数。同样,f(value) 只是 value,然后我们执行 --value
这真是一个令人困惑的 static_if 实现,因为 lambda 中的 f 始终是一个 identity。这是必须的,因为我们无法直接使用 value(无法编写 value.pop_back(),因为那里没有相关名称,所以编译器会自动判断对于整数来说是无效的),因此我们只需将所有对 value 的使用包装在一个相关的函数对象中以延迟实例化(f(value) 取决于 f,因此只有在提供了 f - 如果该函数没有被调用,则不会发生)。最好将其实现为实际传递 lambda 的参数。

1
至少将其称为“id”、“var”或“safe”,而不是“f”。 - Yakk - Adam Nevraumont
这个回答听起来差不多对了,+1,但是它仍然让identity的设计层面的目的成为一个谜。为什么不能传递一个数字42而不是一个identity的实例呢? - Cheers and hth. - Alf
@Cheersandhth.-Alf增加了相关的解释。 - Barry

1
template<typename F>
void then(const F& f){
  f(identity());
}

Is more readable as

template<typename F>
void then(const F& f){
  f(identity{});
}

他们正在构建一个身份对象,而不是调用一个身份对象。
这里的诀窍在于模板函数的非依赖部分必须是有效的,即使该函数从未实例化。
因此,在lambda中说value.pop_back()value为整数时永远无效。
通过将identity{}传递给thenelse中的一个,我们可以避免这个问题。
语句f(value)产生了一个相关类型。因此,它只需要在实际实例化lambda的模板operator()时有效(还必须有一些可能的f使其有效,但这是一个特殊情况)。
由于我们仅实例化条件告诉我们的路径,因此f(value)可以以几乎任何方式使用,只要在取出分支中有效即可。
我会把f改成更好的名字,比如safeguardvarmagic,而不是f。在简洁的代码中使用两个f会增加混淆。

0

让我们来看一个例子,当您的Condstatic_if中为true时,主模板类将被使用...

template<bool Cond>
struct statement {
    template<typename F>
    void then(const F& f){
        f(identity());
    }

    template<typename F>
    void else_(const F&){}
};

请记得,你的调用函数是:

static_if<std::is_same<std::string, T>::value>
(
   [&](auto f){ //This is the lamda passed, it's a generic lambda
        f(value).pop_back();
    }
).else_(
   [&](auto f){
        --f(value);
    }
);

在下面的applying函数中,F是那个通用lambda的一种类型(意思是,你可以使用任何类型来调用f)
template<typename F>
void then(const F& f){
    f(identity());
}

identity()会创建一个identity类型的对象,然后将其作为参数传递给你的泛型lambda函数进行调用

 [&](auto f){ //This is the lamda passed, it's a generic lambda
        f(value).pop_back();
    }

但请记住,f是一个类型为identity的对象,并且具有一个模板化的调用()运算符,它基本上返回传递给它的对象。
所以,我们就这样做:
void decrement_kindof(std::string& value){
    static_if<std::is_same<std::string, std::string>::value>([&](auto f){
        f(value).pop_back();
    }).else_([&](auto f){
        --f(value);
    });
});

简化为:

void decrement_kindof(std::string& value){
    static_if<true>([&](auto f){
        f(value).pop_back();
    }).else_([&](auto f){
        --f(value);
    });
});

简化为:

void decrement_kindof(std::string& value){
    static_if<true>(
        [&](identity ident){
             auto&& x = ident(value); //x is std::string()
             x.pop_back();
       } (identity())  //<-- the lambda is called

    ).else_(
        [&](auto f){    //This is not evaluated, because it's not called by the primary template of `statement`
            --f(value);
        }
    );   
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接