2017-03-14 69 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