2017-03-14 13 views
4

我有一个函数将std::function作为参数。但是,我想确保传入的函数不允许修改传递给它的参数。将lambdas转换为std :: function时强制执行正确性正确性

这里是函数的简单版本(注意:T就可以了,通常,参考):

template <class T> 
void Bar(std::function<void(std::add_const_t<T>)> func) 
{ 
    // ... 
} 

不好的用法:

Bar<int&>([](int&) { /* Do nasty stuff! */ }); /* A-OK! */ 

我希望禁止这种使用,但是这段代码编译得非常好,即使我觉得它不应该。

有趣的事情是,如果我摆脱模板参数,即:

void Bar(std::function<void(const int&)> func) 
{ 
    // ... 
} 

然后,这种用法不会进行编译(因为它不应该):

Bar([](int&) { /* Do nasty stuff! */ }); /* Error C2664 */ 

如何我可以执行此操作并仍保留模板参数吗?

+0

注'的std :: add_const_t ''是INT&'。 – aschepler

+0

@aschepler我在文档中也看到了这一点。所以我甚至试着做一个hacky版本:'const T',我想这会推演到'const int&'。但不是。即使我用'const T'替换'std :: add_const_t ',它仍然编译得很好。 – Zeenobit

+0

@Zeenobit:模板不是宏的。这更类似于'typedef int & T; T const ref;' - 再次'ref'将是'int&'。还要比较指针:'typedef int * T; T const ptr'让你获得一个'int * const'而不是'int const *'。 – MSalters

回答

5

请注意,std::add_const_t<int &>int &,因为您没有将const添加到int。而是将const添加到对int的引用中,并且您获得对int(即int &)的常量引用,而不是对const int的引用。

一个简单的方法来解决它可以是:

#include<functional> 
#include<type_traits> 

template<typename T> 
struct to_const_ { using type = std::add_const_t<T>; }; 

template<typename T> 
struct to_const_<T &> { using type = std::add_const_t<T> &; }; 

template<typename T> 
using to_const_t = typename to_const_<T>::type; 

template <class T> 
void Bar(std::function<void(to_const_t<T>)> func) 
{} 

int main() { 
    Bar<int&>([](int&) {}); 
} 

上面的代码不编译(如需要),除非你把它转化为:

Bar<int&>([](const int &) {}); 

注意,它的工作原理正确地只用左值引用,但如果你有这个想法,增加对右值引用和指针的支持是很直接的。


它遵循最小,(可能)工作示例:

#include<functional> 
#include<type_traits> 

template<typename T> 
struct to_const_ { using type = std::add_const_t<T>; }; 

template<typename T> 
struct to_const_<T &> { using type = std::add_const_t<T> &; }; 

template<typename T> 
struct to_const_<T &&> { using type = std::add_const_t<T> &&; }; 

template<typename T> 
struct to_const_<T * const> { using type = std::conditional_t<std::is_pointer<T>::value, typename to_const_<T>::type * const, std::add_const_t<typename to_const_<T>::type> * const>; }; 

template<typename T> 
struct to_const_<T *> { using type = std::conditional_t<std::is_pointer<T>::value, typename to_const_<T>::type *, std::add_const_t<typename to_const_<T>::type> *>; }; 

template<typename T> 
using to_const_t = typename to_const_<T>::type; 

template <class T> 
void Bar(std::function<void(to_const_t<T>)> func) 
{} 

int main() { 
    Bar<int **>([](const int **) {}); 
} 
+0

这是完美的。谢谢! – Zeenobit

0

在模板的帮助下,您可以使用static_assert编译失败。

//Base case - T is non-const, possibly pointer or reference 
template <typename T> struct is_const_param { static constexpr bool value = !std::is_lvalue_reference<T>::value && !std::is_rvalue_reference<T>::value && !std::is_pointer<T>::value; }; 

//T is const, but possibly pointer to non-const 
template <typename T> struct is_const_param<const T> { static constexpr bool value = !std::is_pointer<T>::value; }; 

//Remove reference, try again 
template <typename T> struct is_const_param<const T&> : is_const_param<const T> { }; 

//Remove reference, try again 
template <typename T> struct is_const_param<const T&&> : is_const_param<const T> { }; 

//Remove pointer, try again 
template <typename T> struct is_const_param<const T*> : is_const_param<const T> { }; 

//Remove pointer, try again 
template <typename T> struct is_const_param<const T* const> : is_const_param<const T> { }; 

static_assert只是看起来像:

template <class T> 
void Bar(std::function<void(T)>) 
{ 
    static_assert(is_const_param<T>::value, "..."); 
} 

此设置为只有当T是成功的:

  1. 按值传递(见下文)。
  2. 通过常量引用。
  3. 通过指针传递到常量。

如果你想看到一些测试用例here如果你想看到什么使它成功或失败的例子。


这是设置允许正常传递值,如Bar<int>。如果要将其更改为仅允许Bar<const int>,请删除is_const_param<const T*>专业化,并将非专用模板中的value的初始化更改为false。你可以看到here

+0

'T **'呢? – skypjack