[INVOKE] C++11 backport of std::invoke, std::invoke_result, std:apply and std::make_from_tuple (#2450)

* added backport of std::invoke, std::invoke_result and std::apply

* added backport of std::invoke, std::invoke_result and std::apply

* msvc doesn't like keyword 'not'

* i think this fixes detection of invoke on MSVC

* ok, i think detection of invoke stuff is fixed on windows

* - just have dlib's own implementation and don't use standard library even if c++17 is enabled.
- added tests for dlib::invoke_result_t

* added docs

* - added dlib::make_from_tuple
- added tests + docs

* - make sure you use the dlib:: namespace. Otherwise, when compiling with C++17, compiler might get confused
- use remove_reference instead of decay. That's what the standard says to use

* added dlib::is_invocable

* - defined invoke_traits. This removes dupplicate code.
- This makes absolutely no difference but is just a tiny bit nicer.

* removed the test that could potentially fail with MSVC

Co-authored-by: pfeatherstone <peter@me>
This commit is contained in:
pfeatherstone 2021-11-06 19:54:01 +00:00 committed by GitHub
parent 7f6746e7cd
commit f77189db03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 444 additions and 0 deletions

145
dlib/invoke.h Normal file
View File

@ -0,0 +1,145 @@
// Copyright (C) 2016 Davis E. King (davis@dlib.net)
// License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_INVOKE_Hh_
#define DLIB_INVOKE_Hh_
#include <functional>
#include <type_traits>
#include "utility.h"
namespace dlib
{
// ----------------------------------------------------------------------------------------
namespace detail {
template< typename F, typename ... Args >
auto INVOKE(F&& fn, Args&& ... args)
-> typename std::enable_if<
std::is_member_pointer<typename std::decay<F>::type>::value,
decltype(std::mem_fn(fn)(std::forward<Args>(args)...))>::type
{
return std::mem_fn(fn)(std::forward<Args>(args)...) ;
}
template< typename F, typename ... Args >
auto INVOKE(F&& fn, Args&& ... args)
-> typename std::enable_if<
!std::is_member_pointer<typename std::decay<F>::type>::value,
decltype(std::forward<F>(fn)(std::forward<Args>(args)...))>::type
{
return std::forward<F>(fn)(std::forward<Args>(args)...);
}
}
template< typename F, typename... Args>
auto invoke(F && f, Args &&... args)
/*!
ensures
- identical to std::invoke(std::forward<F>(f), std::forward<Args>(args)...)
- works with C++11 onwards
!*/
-> decltype(detail::INVOKE(std::forward<F>( f ), std::forward<Args>( args )...))
{
return detail::INVOKE(std::forward<F>( f ), std::forward<Args>( args )...);
}
// ----------------------------------------------------------------------------------------
namespace detail
{
template< typename AlwaysVoid, typename, typename...>
struct invoke_traits
{
static constexpr bool value = false;
};
template< typename F, typename... Args >
struct invoke_traits< decltype( void(dlib::invoke(std::declval<F>(), std::declval<Args>()...)) ), F, Args...>
{
static constexpr bool value = true;
using type = decltype( dlib::invoke(std::declval<F>(), std::declval<Args>()...) );
};
}
template< typename F, typename... Args >
struct invoke_result : detail::invoke_traits< void, F, Args...> {};
/*!
ensures
- identical to std::invoke_result<F, Args..>
- works with C++11 onwards
!*/
template< typename F, typename... Args >
using invoke_result_t = typename invoke_result<F, Args...>::type;
/*!
ensures
- identical to std::invoke_result_t<F, Args..>
- works with C++11 onwards
!*/
// ----------------------------------------------------------------------------------------
template< typename F, typename... Args >
struct is_invocable : std::integral_constant<bool, detail::invoke_traits< void, F, Args...>::value> {};
/*!
ensures
- identical to std::is_invocable<F, Args..>
- works with C++11 onwards
!*/
// ----------------------------------------------------------------------------------------
namespace detail
{
template<typename F, typename Tuple, std::size_t... I>
auto apply_impl(F&& fn, Tuple&& tpl, index_sequence<I...>)
-> decltype(dlib::invoke(std::forward<F>(fn),
std::get<I>(std::forward<Tuple>(tpl))...))
{
return dlib::invoke(std::forward<F>(fn),
std::get<I>(std::forward<Tuple>(tpl))...);
}
}
template<typename F, typename Tuple>
auto apply(F&& fn, Tuple&& tpl)
/*!
ensures
- identical to std::apply(std::forward<F>(f), std::forward<Tuple>(tpl))
- works with C++11 onwards
!*/
-> decltype(detail::apply_impl(std::forward<F>(fn),
std::forward<Tuple>(tpl),
make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{}))
{
return detail::apply_impl(std::forward<F>(fn),
std::forward<Tuple>(tpl),
make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{});
}
// ----------------------------------------------------------------------------------------
namespace detail
{
template <class T, class Tuple, std::size_t... I>
constexpr T make_from_tuple_impl( Tuple&& t, index_sequence<I...> )
{
return T(std::get<I>(std::forward<Tuple>(t))...);
}
}
template <class T, class Tuple>
constexpr T make_from_tuple( Tuple&& t )
/*!
ensures
- identical to std::make_from_tuple<T>(std::forward<Tuple>(t))
- works with C++11 onwards
!*/
{
return detail::make_from_tuple_impl<T>(std::forward<Tuple>(t),
make_index_sequence<std::tuple_size<typename std::remove_reference<Tuple>::type >::value>{});
}
// ----------------------------------------------------------------------------------------
}
#endif //DLIB_INVOKE_Hh_

View File

@ -71,6 +71,7 @@ set (tests
hash_table.cpp
hog_image.cpp
image.cpp
invoke.cpp
iosockstream.cpp
is_same_object.cpp
isotonic_regression.cpp

256
dlib/test/invoke.cpp Normal file
View File

@ -0,0 +1,256 @@
// Copyright (C) 2008 Davis E. King (davis@dlib.net)
// License: Boost Software License See LICENSE.txt for the full license.
#include <string>
#include <memory>
#include <array>
#include <tuple>
#include <utility>
#include <dlib/invoke.h>
#include "tester.h"
namespace
{
using namespace test;
using namespace dlib;
logger dlog("test.invoke");
// ----------------------------------------------------------------------------------------
static const std::string run1_str1 = "hello there 1";
static const std::string run1_str2 = "hello there 2";
static const std::string run1_str3 = "hello there 3";
static const std::string run1_str4 = "hello there 4";
static const std::string run1_str5 = "hello there 5";
void func_testargs(int i, std::string ref1, const std::string& ref2, const std::string& ref3, std::string& ref4)
{
DLIB_TEST(i > 0);
DLIB_TEST(ref1 == run1_str1);
DLIB_TEST(ref2 == run1_str2);
DLIB_TEST(ref3 == run1_str3);
DLIB_TEST(ref4 == run1_str4);
ref4 = run1_str5;
}
int func_return_addition(int i, int j)
{
return i + j;
}
void test_functions()
{
static_assert(dlib::is_invocable<decltype(func_testargs), int, std::string, const std::string&, const std::string&, std::string&>::value, "should be invocable!");
static_assert(dlib::is_invocable<decltype(func_testargs), int, std::string, std::string, const std::string&, std::string&>::value, "should be invocable!");
static_assert(dlib::is_invocable<decltype(func_testargs), int, std::string, std::string, std::string, std::string&>::value, "should be invocable!");
static_assert(dlib::is_invocable<decltype(func_testargs), int, std::string, std::string, std::string, std::reference_wrapper<std::string>>::value, "should be invocable!");
{
std::string str = run1_str4;
dlib::invoke(func_testargs, 1, run1_str1, run1_str2, std::cref(run1_str3), std::ref(str));
DLIB_TEST(str == run1_str5);
}
{
std::string str = run1_str4;
dlib::apply(func_testargs, std::make_tuple(1, run1_str1, run1_str2, std::cref(run1_str3), std::ref(str)));
DLIB_TEST(str == run1_str5);
}
{
for (int i = -10 ; i <= 10 ; i++)
{
for (int j = -10 ; j <= 10 ; j++)
{
DLIB_TEST(dlib::invoke(func_return_addition, i, j) == (i+j));
DLIB_TEST(dlib::apply(func_return_addition, std::make_tuple(i, j)) == (i+j));
}
}
}
}
// ----------------------------------------------------------------------------------------
void test_lambdas()
{
{
std::string str = run1_str4;
dlib::invoke([](int i, std::string ref1, const std::string& ref2, const std::string& ref3, std::string& ref4) {
DLIB_TEST(i > 0);
DLIB_TEST(ref1 == run1_str1);
DLIB_TEST(ref2 == run1_str2);
DLIB_TEST(ref3 == run1_str3);
DLIB_TEST(ref4 == run1_str4);
ref4 = run1_str5;
}, 1, run1_str1, run1_str2, std::cref(run1_str3), std::ref(str));
DLIB_TEST(str == run1_str5);
}
{
std::string str = run1_str4;
dlib::apply([](int i, std::string ref1, const std::string& ref2, const std::string& ref3, std::string& ref4) {
DLIB_TEST(i > 0);
DLIB_TEST(ref1 == run1_str1);
DLIB_TEST(ref2 == run1_str2);
DLIB_TEST(ref3 == run1_str3);
DLIB_TEST(ref4 == run1_str4);
ref4 = run1_str5;
}, std::make_tuple(1, run1_str1, run1_str2, std::cref(run1_str3), std::ref(str)));
DLIB_TEST(str == run1_str5);
}
{
for (int i = -10 ; i <= 10 ; i++)
{
for (int j = -10 ; j <= 10 ; j++)
{
DLIB_TEST(dlib::invoke([](int i, int j) {return i + j;}, i, j) == (i+j));
DLIB_TEST(dlib::apply([](int i, int j) {return i + j;}, std::make_tuple(i,j)) == (i+j));
}
}
}
}
// ----------------------------------------------------------------------------------------
struct example_struct
{
example_struct(int i_ = 0) : i(i_) {}
example_struct(const example_struct&) = delete;
example_struct& operator=(const example_struct&) = delete;
example_struct(example_struct&& other) : i(other.i) {other.i = 0;}
example_struct& operator=(example_struct&& other) {i = other.i; other.i = 0; return *this;}
int get_i() const {return i;}
int i = 0;
};
void test_member_functions_and_data()
{
example_struct obj1(10);
std::unique_ptr<example_struct> obj2(new example_struct(11));
std::shared_ptr<example_struct> obj3(new example_struct(12));
DLIB_TEST(dlib::invoke(&example_struct::get_i, obj1) == 10);
DLIB_TEST(dlib::invoke(&example_struct::i, obj1) == 10);
DLIB_TEST(dlib::invoke(&example_struct::get_i, &obj1) == 10);
DLIB_TEST(dlib::invoke(&example_struct::i, &obj1) == 10);
DLIB_TEST(dlib::invoke(&example_struct::get_i, obj2) == 11);
DLIB_TEST(dlib::invoke(&example_struct::i, obj2) == 11);
DLIB_TEST(dlib::invoke(&example_struct::get_i, obj3) == 12);
DLIB_TEST(dlib::invoke(&example_struct::i, obj3) == 12);
}
// ----------------------------------------------------------------------------------------
int return_int()
{
return 0;
}
int& return_int_ref()
{
static int i = 0;
return i;
}
const int& return_int_const_ref()
{
static const int i = 0;
return i;
}
int* return_int_pointer()
{
static int i = 0;
return &i;
}
const int* return_int_const_pointer()
{
static const int i = 0;
return &i;
}
void test_return_types()
{
static_assert(std::is_same<int, dlib::invoke_result_t<decltype(return_int)>>::value, "bad type");
static_assert(std::is_same<int&, dlib::invoke_result_t<decltype(return_int_ref)>>::value, "bad type");
static_assert(std::is_same<const int&, dlib::invoke_result_t<decltype(return_int_const_ref)>>::value, "bad type");
static_assert(std::is_same<int*, dlib::invoke_result_t<decltype(return_int_pointer)>>::value, "bad type");
static_assert(std::is_same<const int*, dlib::invoke_result_t<decltype(return_int_const_pointer)>>::value, "bad type");
static_assert(std::is_same<int, dlib::invoke_result_t<decltype(&example_struct::get_i), const example_struct&>>::value, "bad type");
static_assert(std::is_same<int, dlib::invoke_result_t<decltype(&example_struct::get_i), example_struct&>>::value, "bad type");
static_assert(std::is_same<int, dlib::invoke_result_t<decltype(&example_struct::get_i), const example_struct*>>::value, "bad type");
static_assert(std::is_same<int, dlib::invoke_result_t<decltype(&example_struct::get_i), example_struct*>>::value, "bad type");
static_assert(std::is_same<int, dlib::invoke_result_t<decltype(&example_struct::get_i), std::unique_ptr<example_struct>>>::value, "bad type");
static_assert(std::is_same<int, dlib::invoke_result_t<decltype(&example_struct::get_i), std::shared_ptr<example_struct>>>::value, "bad type");
static_assert(std::is_same<const int&, dlib::invoke_result_t<decltype(&example_struct::i), const example_struct&>>::value, "bad type");
static_assert(std::is_same<int&, dlib::invoke_result_t<decltype(&example_struct::i), example_struct&>>::value, "bad type");
static_assert(std::is_same<const int&, dlib::invoke_result_t<decltype(&example_struct::i), const example_struct*>>::value, "bad type");
static_assert(std::is_same<int&, dlib::invoke_result_t<decltype(&example_struct::i), example_struct*>>::value, "bad type");
static_assert(std::is_same<int&, dlib::invoke_result_t<decltype(&example_struct::i), std::unique_ptr<example_struct>>>::value, "bad type");
static_assert(std::is_same<int&, dlib::invoke_result_t<decltype(&example_struct::i), std::shared_ptr<example_struct>>>::value, "bad type");
auto lambda_func_return_int = []() -> int {return 0;};
static_assert(std::is_same<int, dlib::invoke_result_t<decltype(lambda_func_return_int)>>::value, "bad type");
}
// ----------------------------------------------------------------------------------------
void test_make_from_tuple()
{
struct multi_args_object
{
multi_args_object(int i_, int j_) : i(i_), j(j_) {}
int i = 0;
int j = 0;
};
{
auto obj = dlib::make_from_tuple<multi_args_object>(std::make_tuple(1, 2));
static_assert(std::is_same<decltype(obj), multi_args_object>::value, "bad type");
DLIB_TEST(obj.i == 1);
DLIB_TEST(obj.j == 2);
}
{
std::array<int,2> a = {3, 4};
auto obj = dlib::make_from_tuple<multi_args_object>(a);
static_assert(std::is_same<decltype(obj), multi_args_object>::value, "bad type");
DLIB_TEST(obj.i == 3);
DLIB_TEST(obj.j == 4);
}
{
auto obj = dlib::make_from_tuple<multi_args_object>(std::make_pair(5, 6));
static_assert(std::is_same<decltype(obj), multi_args_object>::value, "bad type");
DLIB_TEST(obj.i == 5);
DLIB_TEST(obj.j == 6);
}
}
// ----------------------------------------------------------------------------------------
class invoke_tester : public tester
{
public:
invoke_tester(
) : tester("test_invoke",
"Runs tests on dlib::invoke and dlib::apply")
{}
void perform_test(
)
{
test_functions();
test_lambdas();
test_member_functions_and_data();
test_return_types();
test_make_from_tuple();
}
} a;
}

42
dlib/utility.h Normal file
View File

@ -0,0 +1,42 @@
// Copyright (C) 2016 Davis E. King (davis@dlib.net)
// License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_UTILITY_Hh_
#define DLIB_UTILITY_Hh_
#include <cstddef>
/*
This header contains back-ports of C++14/17 functions and type traits
found in <utility> header of the standard library.
*/
namespace dlib
{
template<std::size_t... Ints>
struct index_sequence
{
using type = index_sequence;
using value_type = std::size_t;
static constexpr std::size_t size() noexcept {return sizeof...(Ints);}
};
template<class Sequence1, class Sequence2>
struct merge_and_renumber;
template<std::size_t... I1, std::size_t... I2>
struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>>
: index_sequence < I1..., (sizeof...(I1) + I2)... > {};
template<std::size_t N>
struct make_index_sequence
: merge_and_renumber < typename make_index_sequence < N / 2 >::type,
typename make_index_sequence < N - N / 2 >::type > {};
template<> struct make_index_sequence<0> : index_sequence<> {};
template<> struct make_index_sequence<1> : index_sequence<0> {};
template<typename... Ts>
using index_sequence_for = make_index_sequence<sizeof...(Ts)>;
}
#endif //DLIB_UTILITY_Hh_