Commit 88742563 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

Backport invocability variable templates

Summary: [Folly] Backport invocability variable templates: `is_invocable_v`, `is_invocable_r_v`, `is_nothrow_invocable_v`, `is_nothrow_invocable_r_v`. And for the member-invoke and free-invoke traits.

Reviewed By: ericniebler

Differential Revision: D17950331

fbshipit-source-id: 5409d7a72116b0976573e1f64760c5ef9274966f
parent d9b854ef
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include <functional> #include <functional>
#include <type_traits> #include <type_traits>
#include <folly/Portability.h>
#include <folly/Preprocessor.h> #include <folly/Preprocessor.h>
#include <folly/Traits.h> #include <folly/Traits.h>
...@@ -28,9 +29,13 @@ ...@@ -28,9 +29,13 @@
* * std::invoke_result * * std::invoke_result
* * std::invoke_result_t * * std::invoke_result_t
* * std::is_invocable * * std::is_invocable
* * std::is_invocable_v
* * std::is_invocable_r * * std::is_invocable_r
* * std::is_invocable_r_v
* * std::is_nothrow_invocable * * std::is_nothrow_invocable
* * std::is_nothrow_invocable_v
* * std::is_nothrow_invocable_r * * std::is_nothrow_invocable_r
* * std::is_nothrow_invocable_r_v
*/ */
#if __cpp_lib_invoke >= 201411 || _MSC_VER #if __cpp_lib_invoke >= 201411 || _MSC_VER
...@@ -71,8 +76,12 @@ namespace folly { ...@@ -71,8 +76,12 @@ namespace folly {
/* using override */ using std::invoke_result_t; /* using override */ using std::invoke_result_t;
/* using override */ using std::is_invocable; /* using override */ using std::is_invocable;
/* using override */ using std::is_invocable_r; /* using override */ using std::is_invocable_r;
/* using override */ using std::is_invocable_r_v;
/* using override */ using std::is_invocable_v;
/* using override */ using std::is_nothrow_invocable; /* using override */ using std::is_nothrow_invocable;
/* using override */ using std::is_nothrow_invocable_r; /* using override */ using std::is_nothrow_invocable_r;
/* using override */ using std::is_nothrow_invocable_r_v;
/* using override */ using std::is_nothrow_invocable_v;
} // namespace folly } // namespace folly
...@@ -145,20 +154,40 @@ using invoke_result_t = typename invoke_result<F, Args...>::type; ...@@ -145,20 +154,40 @@ using invoke_result_t = typename invoke_result<F, Args...>::type;
template <typename F, typename... Args> template <typename F, typename... Args>
struct is_invocable : invoke_detail::is_invocable<void, F, Args...> {}; struct is_invocable : invoke_detail::is_invocable<void, F, Args...> {};
// mimic: std::is_invocable_v, C++17
template <typename F, typename... Args>
FOLLY_INLINE_VARIABLE constexpr bool is_invocable_v =
is_invocable<F, Args...>::value;
// mimic: std::is_invocable_r, C++17 // mimic: std::is_invocable_r, C++17
template <typename R, typename F, typename... Args> template <typename R, typename F, typename... Args>
struct is_invocable_r : invoke_detail::is_invocable_r<void, R, F, Args...> {}; struct is_invocable_r : invoke_detail::is_invocable_r<void, R, F, Args...> {};
// mimic: std::is_invocable_r_v, C++17
template <typename R, typename F, typename... Args>
FOLLY_INLINE_VARIABLE constexpr bool is_invocable_r_v =
is_invocable_r<R, F, Args...>::value;
// mimic: std::is_nothrow_invocable, C++17 // mimic: std::is_nothrow_invocable, C++17
template <typename F, typename... Args> template <typename F, typename... Args>
struct is_nothrow_invocable struct is_nothrow_invocable
: invoke_detail::is_nothrow_invocable<void, F, Args...> {}; : invoke_detail::is_nothrow_invocable<void, F, Args...> {};
// mimic: std::is_nothrow_invocable_v, C++17
template <typename F, typename... Args>
FOLLY_INLINE_VARIABLE constexpr bool is_nothrow_invocable_v =
is_nothrow_invocable<F, Args...>::value;
// mimic: std::is_nothrow_invocable_r, C++17 // mimic: std::is_nothrow_invocable_r, C++17
template <typename R, typename F, typename... Args> template <typename R, typename F, typename... Args>
struct is_nothrow_invocable_r struct is_nothrow_invocable_r
: invoke_detail::is_nothrow_invocable_r<void, R, F, Args...> {}; : invoke_detail::is_nothrow_invocable_r<void, R, F, Args...> {};
// mimic: std::is_nothrow_invocable_r_v, C++17
template <typename R, typename F, typename... Args>
FOLLY_INLINE_VARIABLE constexpr bool is_nothrow_invocable_r_v =
is_nothrow_invocable_r<R, F, Args...>::value;
} // namespace folly } // namespace folly
#endif #endif
...@@ -177,13 +206,25 @@ struct free_invoke_proxy { ...@@ -177,13 +206,25 @@ struct free_invoke_proxy {
using invoke_result_t = folly::invoke_result_t<Invoke, Args...>; using invoke_result_t = folly::invoke_result_t<Invoke, Args...>;
template <typename... Args> template <typename... Args>
struct is_invocable : folly::is_invocable<Invoke, Args...> {}; struct is_invocable : folly::is_invocable<Invoke, Args...> {};
template <typename... Args>
FOLLY_INLINE_VARIABLE static constexpr bool is_invocable_v =
is_invocable<Args...>::value;
template <typename R, typename... Args> template <typename R, typename... Args>
struct is_invocable_r : folly::is_invocable_r<R, Invoke, Args...> {}; struct is_invocable_r : folly::is_invocable_r<R, Invoke, Args...> {};
template <typename R, typename... Args>
FOLLY_INLINE_VARIABLE static constexpr bool is_invocable_r_v =
is_invocable_r<R, Args...>::value;
template <typename... Args> template <typename... Args>
struct is_nothrow_invocable : folly::is_nothrow_invocable<Invoke, Args...> {}; struct is_nothrow_invocable : folly::is_nothrow_invocable<Invoke, Args...> {};
template <typename... Args>
FOLLY_INLINE_VARIABLE static constexpr bool is_nothrow_invocable_v =
is_nothrow_invocable<Args...>::value;
template <typename R, typename... Args> template <typename R, typename... Args>
struct is_nothrow_invocable_r struct is_nothrow_invocable_r
: folly::is_nothrow_invocable_r<R, Invoke, Args...> {}; : folly::is_nothrow_invocable_r<R, Invoke, Args...> {};
template <typename R, typename... Args>
FOLLY_INLINE_VARIABLE static constexpr bool is_nothrow_invocable_r_v =
is_nothrow_invocable_r<R, Args...>::value;
template <typename... Args> template <typename... Args>
static constexpr auto invoke(Args&&... args) noexcept( static constexpr auto invoke(Args&&... args) noexcept(
...@@ -207,9 +248,13 @@ struct free_invoke_proxy { ...@@ -207,9 +248,13 @@ struct free_invoke_proxy {
* * invoke_result * * invoke_result
* * invoke_result_t * * invoke_result_t
* * is_invocable * * is_invocable
* * is_invocable_v
* * is_invocable_r * * is_invocable_r
* * is_invocable_r_v
* * is_nothrow_invocable * * is_nothrow_invocable
* * is_nothrow_invocable_v
* * is_nothrow_invocable_r * * is_nothrow_invocable_r
* * is_nothrow_invocable_r_v
* *
* The container also has a static member function: * The container also has a static member function:
* *
...@@ -246,19 +291,19 @@ struct free_invoke_proxy { ...@@ -246,19 +291,19 @@ struct free_invoke_proxy {
* traits::invoke_result<Deep::CanFoo, Bar&&> // empty * traits::invoke_result<Deep::CanFoo, Bar&&> // empty
* traits::invoke_result_t<Deep::CanFoo, Bar&&> // error * traits::invoke_result_t<Deep::CanFoo, Bar&&> // error
* *
* traits::is_invocable<CanFoo, Bar&>::value // true * traits::is_invocable_v<CanFoo, Bar&> // true
* traits::is_invocable<CanFoo, Bar&&>::value // false * traits::is_invocable_v<CanFoo, Bar&&> // false
* *
* traits::is_invocable_r<int, CanFoo, Bar&>::value // true * traits::is_invocable_r_v<int, CanFoo, Bar&> // true
* traits::is_invocable_r<char*, CanFoo, Bar&>::value // false * traits::is_invocable_r_v<char*, CanFoo, Bar&> // false
* *
* traits::is_nothrow_invocable<CanFoo, Bar&>::value // false * traits::is_nothrow_invocable_v<CanFoo, Bar&> // false
* traits::is_nothrow_invocable<CanFoo, Car&&>::value // true * traits::is_nothrow_invocable_v<CanFoo, Car&&> // true
* *
* traits::is_nothrow_invocable<int, CanFoo, Bar&>::value // false * traits::is_nothrow_invocable_v<int, CanFoo, Bar&> // false
* traits::is_nothrow_invocable<char*, CanFoo, Bar&>::value // false * traits::is_nothrow_invocable_v<char*, CanFoo, Bar&> // false
* traits::is_nothrow_invocable<int, CanFoo, Car&&>::value // true * traits::is_nothrow_invocable_v<int, CanFoo, Car&&> // true
* traits::is_nothrow_invocable<char*, CanFoo, Car&&>::value // false * traits::is_nothrow_invocable_v<char*, CanFoo, Car&&> // false
* *
* When a name has a primary definition in a fixed namespace and alternate * When a name has a primary definition in a fixed namespace and alternate
* definitions in the namespaces of its arguments, the primary definition may * definitions in the namespaces of its arguments, the primary definition may
...@@ -316,14 +361,26 @@ struct member_invoke_proxy { ...@@ -316,14 +361,26 @@ struct member_invoke_proxy {
using invoke_result_t = folly::invoke_result_t<Invoke, O, Args...>; using invoke_result_t = folly::invoke_result_t<Invoke, O, Args...>;
template <typename O, typename... Args> template <typename O, typename... Args>
struct is_invocable : folly::is_invocable<Invoke, O, Args...> {}; struct is_invocable : folly::is_invocable<Invoke, O, Args...> {};
template <typename O, typename... Args>
FOLLY_INLINE_VARIABLE static constexpr bool is_invocable_v =
is_invocable<O, Args...>::value;
template <typename R, typename O, typename... Args> template <typename R, typename O, typename... Args>
struct is_invocable_r : folly::is_invocable_r<R, Invoke, O, Args...> {}; struct is_invocable_r : folly::is_invocable_r<R, Invoke, O, Args...> {};
template <typename R, typename O, typename... Args>
FOLLY_INLINE_VARIABLE static constexpr bool is_invocable_r_v =
is_invocable_r<R, O, Args...>::value;
template <typename O, typename... Args> template <typename O, typename... Args>
struct is_nothrow_invocable struct is_nothrow_invocable
: folly::is_nothrow_invocable<Invoke, O, Args...> {}; : folly::is_nothrow_invocable<Invoke, O, Args...> {};
template <typename O, typename... Args>
FOLLY_INLINE_VARIABLE static constexpr bool is_nothrow_invocable_v =
is_nothrow_invocable<O, Args...>::value;
template <typename R, typename O, typename... Args> template <typename R, typename O, typename... Args>
struct is_nothrow_invocable_r struct is_nothrow_invocable_r
: folly::is_nothrow_invocable_r<R, Invoke, O, Args...> {}; : folly::is_nothrow_invocable_r<R, Invoke, O, Args...> {};
template <typename R, typename O, typename... Args>
FOLLY_INLINE_VARIABLE static constexpr bool is_nothrow_invocable_r_v =
is_nothrow_invocable_r<R, O, Args...>::value;
template <typename O, typename... Args> template <typename O, typename... Args>
static constexpr auto invoke(O&& o, Args&&... args) noexcept( static constexpr auto invoke(O&& o, Args&&... args) noexcept(
...@@ -347,9 +404,13 @@ struct member_invoke_proxy { ...@@ -347,9 +404,13 @@ struct member_invoke_proxy {
* * invoke_result * * invoke_result
* * invoke_result_t * * invoke_result_t
* * is_invocable * * is_invocable
* * is_invocable_v
* * is_invocable_r * * is_invocable_r
* * is_invocable_r_v
* * is_nothrow_invocable * * is_nothrow_invocable
* * is_nothrow_invocable_v
* * is_nothrow_invocable_r * * is_nothrow_invocable_r
* * is_nothrow_invocable_r_v
* *
* The container also has a static member function: * The container also has a static member function:
* *
...@@ -385,19 +446,19 @@ struct member_invoke_proxy { ...@@ -385,19 +446,19 @@ struct member_invoke_proxy {
* traits::invoke_result<CanFoo, Bar&&> // empty * traits::invoke_result<CanFoo, Bar&&> // empty
* traits::invoke_result_t<CanFoo, Bar&&> // error * traits::invoke_result_t<CanFoo, Bar&&> // error
* *
* traits::is_invocable<CanFoo, Bar&>::value // true * traits::is_invocable_v<CanFoo, Bar&> // true
* traits::is_invocable<CanFoo, Bar&&>::value // false * traits::is_invocable_v<CanFoo, Bar&&> // false
* *
* traits::is_invocable_r<int, CanFoo, Bar&>::value // true * traits::is_invocable_r_v<int, CanFoo, Bar&> // true
* traits::is_invocable_r<char*, CanFoo, Bar&>::value // false * traits::is_invocable_r_v<char*, CanFoo, Bar&> // false
* *
* traits::is_nothrow_invocable<CanFoo, Bar&>::value // false * traits::is_nothrow_invocable_v<CanFoo, Bar&> // false
* traits::is_nothrow_invocable<CanFoo, Car&&>::value // true * traits::is_nothrow_invocable_v<CanFoo, Car&&> // true
* *
* traits::is_nothrow_invocable<int, CanFoo, Bar&>::value // false * traits::is_nothrow_invocable_v<int, CanFoo, Bar&> // false
* traits::is_nothrow_invocable<char*, CanFoo, Bar&>::value // false * traits::is_nothrow_invocable_v<char*, CanFoo, Bar&> // false
* traits::is_nothrow_invocable<int, CanFoo, Car&&>::value // true * traits::is_nothrow_invocable_v<int, CanFoo, Car&&> // true
* traits::is_nothrow_invocable<char*, CanFoo, Car&&>::value // false * traits::is_nothrow_invocable_v<char*, CanFoo, Car&&> // false
*/ */
#define FOLLY_CREATE_MEMBER_INVOKE_TRAITS(classname, membername) \ #define FOLLY_CREATE_MEMBER_INVOKE_TRAITS(classname, membername) \
struct classname##__folly_detail_member_invoke { \ struct classname##__folly_detail_member_invoke { \
......
...@@ -114,27 +114,27 @@ TEST_F(InvokeTest, invoke_result) { ...@@ -114,27 +114,27 @@ TEST_F(InvokeTest, invoke_result) {
} }
TEST_F(InvokeTest, is_invocable) { TEST_F(InvokeTest, is_invocable) {
EXPECT_TRUE((folly::is_invocable<Fn, int, char>::value)); EXPECT_TRUE((folly::is_invocable_v<Fn, int, char>));
EXPECT_TRUE((folly::is_invocable<Fn, int, char*>::value)); EXPECT_TRUE((folly::is_invocable_v<Fn, int, char*>));
EXPECT_FALSE((folly::is_invocable<Fn, int>::value)); EXPECT_FALSE((folly::is_invocable_v<Fn, int>));
} }
TEST_F(InvokeTest, is_invocable_r) { TEST_F(InvokeTest, is_invocable_r) {
EXPECT_TRUE((folly::is_invocable_r<int, Fn, int, char>::value)); EXPECT_TRUE((folly::is_invocable_r_v<int, Fn, int, char>));
EXPECT_TRUE((folly::is_invocable_r<int, Fn, int, char*>::value)); EXPECT_TRUE((folly::is_invocable_r_v<int, Fn, int, char*>));
EXPECT_FALSE((folly::is_invocable_r<int, Fn, int>::value)); EXPECT_FALSE((folly::is_invocable_r_v<int, Fn, int>));
} }
TEST_F(InvokeTest, is_nothrow_invocable) { TEST_F(InvokeTest, is_nothrow_invocable) {
EXPECT_TRUE((folly::is_nothrow_invocable<Fn, int, char>::value)); EXPECT_TRUE((folly::is_nothrow_invocable_v<Fn, int, char>));
EXPECT_FALSE((folly::is_nothrow_invocable<Fn, int, char*>::value)); EXPECT_FALSE((folly::is_nothrow_invocable_v<Fn, int, char*>));
EXPECT_FALSE((folly::is_nothrow_invocable<Fn, int>::value)); EXPECT_FALSE((folly::is_nothrow_invocable_v<Fn, int>));
} }
TEST_F(InvokeTest, is_nothrow_invocable_r) { TEST_F(InvokeTest, is_nothrow_invocable_r) {
EXPECT_TRUE((folly::is_nothrow_invocable_r<int, Fn, int, char>::value)); EXPECT_TRUE((folly::is_nothrow_invocable_r_v<int, Fn, int, char>));
EXPECT_FALSE((folly::is_nothrow_invocable_r<int, Fn, int, char*>::value)); EXPECT_FALSE((folly::is_nothrow_invocable_r_v<int, Fn, int, char*>));
EXPECT_FALSE((folly::is_nothrow_invocable_r<int, Fn, int>::value)); EXPECT_FALSE((folly::is_nothrow_invocable_r_v<int, Fn, int>));
} }
TEST_F(InvokeTest, free_invoke) { TEST_F(InvokeTest, free_invoke) {
...@@ -161,38 +161,37 @@ TEST_F(InvokeTest, free_invoke_result) { ...@@ -161,38 +161,37 @@ TEST_F(InvokeTest, free_invoke_result) {
TEST_F(InvokeTest, free_is_invocable) { TEST_F(InvokeTest, free_is_invocable) {
using traits = go_invoke_traits; using traits = go_invoke_traits;
EXPECT_TRUE((traits::is_invocable<x::Obj, int>::value)); EXPECT_TRUE((traits::is_invocable_v<x::Obj, int>));
EXPECT_TRUE((traits::is_invocable<y::Obj, char const*>::value)); EXPECT_TRUE((traits::is_invocable_v<y::Obj, char const*>));
EXPECT_FALSE((traits::is_invocable<z::Obj, int>::value)); EXPECT_FALSE((traits::is_invocable_v<z::Obj, int>));
EXPECT_FALSE((traits::is_invocable<float>::value)); EXPECT_FALSE((traits::is_invocable_v<float>));
} }
TEST_F(InvokeTest, free_is_invocable_r) { TEST_F(InvokeTest, free_is_invocable_r) {
using traits = go_invoke_traits; using traits = go_invoke_traits;
EXPECT_TRUE((traits::is_invocable_r<int, x::Obj, int>::value)); EXPECT_TRUE((traits::is_invocable_r_v<int, x::Obj, int>));
EXPECT_TRUE((traits::is_invocable_r<char, y::Obj, char const*>::value)); EXPECT_TRUE((traits::is_invocable_r_v<char, y::Obj, char const*>));
EXPECT_FALSE((traits::is_invocable_r<float, z::Obj, int>::value)); EXPECT_FALSE((traits::is_invocable_r_v<float, z::Obj, int>));
EXPECT_FALSE((traits::is_invocable_r<from_any, float>::value)); EXPECT_FALSE((traits::is_invocable_r_v<from_any, float>));
} }
TEST_F(InvokeTest, free_is_nothrow_invocable) { TEST_F(InvokeTest, free_is_nothrow_invocable) {
using traits = go_invoke_traits; using traits = go_invoke_traits;
EXPECT_TRUE((traits::is_nothrow_invocable<x::Obj, int>::value)); EXPECT_TRUE((traits::is_nothrow_invocable_v<x::Obj, int>));
EXPECT_FALSE((traits::is_nothrow_invocable<y::Obj, char const*>::value)); EXPECT_FALSE((traits::is_nothrow_invocable_v<y::Obj, char const*>));
EXPECT_FALSE((traits::is_nothrow_invocable<z::Obj, int>::value)); EXPECT_FALSE((traits::is_nothrow_invocable_v<z::Obj, int>));
EXPECT_FALSE((traits::is_nothrow_invocable<float>::value)); EXPECT_FALSE((traits::is_nothrow_invocable_v<float>));
} }
TEST_F(InvokeTest, free_is_nothrow_invocable_r) { TEST_F(InvokeTest, free_is_nothrow_invocable_r) {
using traits = go_invoke_traits; using traits = go_invoke_traits;
EXPECT_TRUE((traits::is_nothrow_invocable_r<int, x::Obj, int>::value)); EXPECT_TRUE((traits::is_nothrow_invocable_r_v<int, x::Obj, int>));
EXPECT_FALSE( EXPECT_FALSE((traits::is_nothrow_invocable_r_v<char, y::Obj, char const*>));
(traits::is_nothrow_invocable_r<char, y::Obj, char const*>::value)); EXPECT_FALSE((traits::is_nothrow_invocable_r_v<float, z::Obj, int>));
EXPECT_FALSE((traits::is_nothrow_invocable_r<float, z::Obj, int>::value)); EXPECT_FALSE((traits::is_nothrow_invocable_r_v<from_any, float>));
EXPECT_FALSE((traits::is_nothrow_invocable_r<from_any, float>::value));
} }
TEST_F(InvokeTest, free_invoke_swap) { TEST_F(InvokeTest, free_invoke_swap) {
...@@ -242,31 +241,31 @@ TEST_F(InvokeTest, member_invoke_result) { ...@@ -242,31 +241,31 @@ TEST_F(InvokeTest, member_invoke_result) {
TEST_F(InvokeTest, member_is_invocable) { TEST_F(InvokeTest, member_is_invocable) {
using traits = test_invoke_traits; using traits = test_invoke_traits;
EXPECT_TRUE((traits::is_invocable<Obj, int, char>::value)); EXPECT_TRUE((traits::is_invocable_v<Obj, int, char>));
EXPECT_TRUE((traits::is_invocable<Obj, int, char*>::value)); EXPECT_TRUE((traits::is_invocable_v<Obj, int, char*>));
EXPECT_FALSE((traits::is_invocable<Obj, int>::value)); EXPECT_FALSE((traits::is_invocable_v<Obj, int>));
} }
TEST_F(InvokeTest, member_is_invocable_r) { TEST_F(InvokeTest, member_is_invocable_r) {
using traits = test_invoke_traits; using traits = test_invoke_traits;
EXPECT_TRUE((traits::is_invocable_r<int, Obj, int, char>::value)); EXPECT_TRUE((traits::is_invocable_r_v<int, Obj, int, char>));
EXPECT_TRUE((traits::is_invocable_r<int, Obj, int, char*>::value)); EXPECT_TRUE((traits::is_invocable_r_v<int, Obj, int, char*>));
EXPECT_FALSE((traits::is_invocable_r<int, Obj, int>::value)); EXPECT_FALSE((traits::is_invocable_r_v<int, Obj, int>));
} }
TEST_F(InvokeTest, member_is_nothrow_invocable) { TEST_F(InvokeTest, member_is_nothrow_invocable) {
using traits = test_invoke_traits; using traits = test_invoke_traits;
EXPECT_TRUE((traits::is_nothrow_invocable<Obj, int, char>::value)); EXPECT_TRUE((traits::is_nothrow_invocable_v<Obj, int, char>));
EXPECT_FALSE((traits::is_nothrow_invocable<Obj, int, char*>::value)); EXPECT_FALSE((traits::is_nothrow_invocable_v<Obj, int, char*>));
EXPECT_FALSE((traits::is_nothrow_invocable<Obj, int>::value)); EXPECT_FALSE((traits::is_nothrow_invocable_v<Obj, int>));
} }
TEST_F(InvokeTest, member_is_nothrow_invocable_r) { TEST_F(InvokeTest, member_is_nothrow_invocable_r) {
using traits = test_invoke_traits; using traits = test_invoke_traits;
EXPECT_TRUE((traits::is_nothrow_invocable_r<int, Obj, int, char>::value)); EXPECT_TRUE((traits::is_nothrow_invocable_r_v<int, Obj, int, char>));
EXPECT_FALSE((traits::is_nothrow_invocable_r<int, Obj, int, char*>::value)); EXPECT_FALSE((traits::is_nothrow_invocable_r_v<int, Obj, int, char*>));
EXPECT_FALSE((traits::is_nothrow_invocable_r<int, Obj, int>::value)); EXPECT_FALSE((traits::is_nothrow_invocable_r_v<int, Obj, int>));
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment