2015-04-20 17:51:00 +10:00
|
|
|
/*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*
|
2016-04-27 16:05:16 +10:00
|
|
|
* Copyright 2015-2016 Danny Robson <danny@nerdcruft.net>
|
2015-04-20 17:51:00 +10:00
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef __UTIL_INTROSPECTION_HPP
|
|
|
|
#define __UTIL_INTROSPECTION_HPP
|
|
|
|
|
2016-03-11 19:16:35 +11:00
|
|
|
#include "./preprocessor.hpp"
|
|
|
|
#include "./variadic.hpp"
|
2015-04-20 17:51:00 +10:00
|
|
|
|
|
|
|
#include <cstddef>
|
2016-04-27 16:05:16 +10:00
|
|
|
#include <cstdint>
|
2015-04-20 17:51:00 +10:00
|
|
|
#include <string>
|
|
|
|
#include <tuple>
|
2017-01-31 20:30:16 +11:00
|
|
|
#include <experimental/filesystem>
|
|
|
|
|
2015-04-20 17:51:00 +10:00
|
|
|
|
|
|
|
namespace util {
|
2016-12-21 16:46:16 +11:00
|
|
|
// XXX: clang-3.9/clang-4.0 will not instantiate static constexpr member
|
|
|
|
// variables from class specialisations, so we have to use detail classes
|
|
|
|
// to hold the variables and instantiate _those_ members instead.
|
2016-08-13 21:02:57 +10:00
|
|
|
template <typename T>
|
|
|
|
struct type_name;
|
2016-03-11 13:02:52 +11:00
|
|
|
|
2016-12-21 16:46:16 +11:00
|
|
|
#define CLANG_WORKAROUND(TYPE,TAG,NAME) \
|
|
|
|
namespace detail { \
|
|
|
|
struct type_name_##TAG { \
|
|
|
|
static constexpr const char value[] = (NAME); \
|
|
|
|
}; \
|
|
|
|
} \
|
|
|
|
\
|
|
|
|
template <> \
|
|
|
|
struct type_name<TYPE> : public detail::type_name_##TAG { };
|
2016-03-11 13:15:50 +11:00
|
|
|
|
2016-12-21 16:46:16 +11:00
|
|
|
CLANG_WORKAROUND(bool,bool,"bool")
|
|
|
|
CLANG_WORKAROUND(char,char,"char")
|
|
|
|
CLANG_WORKAROUND(void*,voidp,"void*")
|
2016-04-27 16:05:16 +10:00
|
|
|
|
2016-12-21 16:46:16 +11:00
|
|
|
CLANG_WORKAROUND( uint8_t, u08, "u08")
|
|
|
|
CLANG_WORKAROUND(uint16_t, u16, "u16")
|
|
|
|
CLANG_WORKAROUND(uint32_t, u32, "u32")
|
|
|
|
CLANG_WORKAROUND(uint64_t, u64, "u64")
|
2016-03-11 13:15:50 +11:00
|
|
|
|
2016-12-21 16:46:16 +11:00
|
|
|
CLANG_WORKAROUND( int8_t, s08, "s08")
|
|
|
|
CLANG_WORKAROUND(int16_t, s16, "s16")
|
|
|
|
CLANG_WORKAROUND(int32_t, s32, "s32")
|
|
|
|
CLANG_WORKAROUND(int64_t, s64, "s64")
|
2016-03-11 13:15:50 +11:00
|
|
|
|
2016-12-21 16:46:16 +11:00
|
|
|
CLANG_WORKAROUND(float, f32, "f32")
|
|
|
|
CLANG_WORKAROUND(double, f64, "f64")
|
2016-03-11 13:02:52 +11:00
|
|
|
|
2016-12-21 16:46:16 +11:00
|
|
|
CLANG_WORKAROUND(const char*, const_cstring, "cstring")
|
|
|
|
CLANG_WORKAROUND(char*, cstring, "cstring")
|
|
|
|
CLANG_WORKAROUND(std::string, string, "string")
|
2017-01-31 20:30:16 +11:00
|
|
|
CLANG_WORKAROUND(std::experimental::filesystem::path, path, "path");
|
2016-12-21 16:46:16 +11:00
|
|
|
|
|
|
|
#undef CLANG_WORKAROUND
|
2016-03-14 22:33:22 +11:00
|
|
|
|
2016-03-16 19:27:22 +11:00
|
|
|
template <typename T>
|
|
|
|
constexpr
|
2016-12-21 16:46:16 +11:00
|
|
|
auto type_name_v = type_name<T>::value;
|
2016-03-16 19:27:22 +11:00
|
|
|
|
2016-03-11 13:02:52 +11:00
|
|
|
template <typename T>
|
2016-04-27 16:05:16 +10:00
|
|
|
const char*
|
2016-03-11 13:02:52 +11:00
|
|
|
to_string (void)
|
2016-07-28 13:45:09 +10:00
|
|
|
{ return type_name_v<T>; }
|
2016-04-27 16:05:16 +10:00
|
|
|
|
2016-03-11 13:02:52 +11:00
|
|
|
|
2017-02-09 16:48:42 +11:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
template <typename T>
|
|
|
|
struct type_char;
|
|
|
|
|
|
|
|
template <> struct type_char<float > { static constexpr char value = 'f'; };
|
|
|
|
template <> struct type_char<double> { static constexpr char value = 'd'; };
|
|
|
|
|
|
|
|
template <> struct type_char< int8_t> { static constexpr char value = 'i'; };
|
|
|
|
template <> struct type_char< int16_t> { static constexpr char value = 'i'; };
|
|
|
|
template <> struct type_char< int32_t> { static constexpr char value = 'i'; };
|
|
|
|
template <> struct type_char< int64_t> { static constexpr char value = 'i'; };
|
|
|
|
|
|
|
|
template <> struct type_char< uint8_t> { static constexpr char value = 'u'; };
|
|
|
|
template <> struct type_char<uint16_t> { static constexpr char value = 'u'; };
|
|
|
|
template <> struct type_char<uint32_t> { static constexpr char value = 'u'; };
|
|
|
|
template <> struct type_char<uint64_t> { static constexpr char value = 'u'; };
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
constexpr auto type_char_v = type_char<T>::value;
|
|
|
|
|
2016-03-11 13:02:52 +11:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Lists valid values of an enumeration
|
|
|
|
///
|
|
|
|
/// E: enumeration type
|
2016-12-21 16:46:16 +11:00
|
|
|
///
|
|
|
|
/// Specialisations must provide the following constexpr:
|
|
|
|
/// value_type: typename
|
|
|
|
/// value_count: size_t
|
|
|
|
/// values: static const std::array<value_type,value_count>
|
2016-03-11 13:02:52 +11:00
|
|
|
template <
|
|
|
|
typename E
|
|
|
|
>
|
2016-12-21 16:46:16 +11:00
|
|
|
struct enum_traits;
|
2016-03-11 13:02:52 +11:00
|
|
|
|
2016-03-11 19:16:35 +11:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Defines specialisations for introspection data structures for an
|
|
|
|
/// enum E, in namespace NS, with variadic values __VA_ARGS__.
|
|
|
|
///
|
|
|
|
/// Expects to be caleld from outside all namespaces.
|
2016-12-21 16:46:16 +11:00
|
|
|
///
|
|
|
|
/// XXX: If we define the constexpr fields in a template specialised class
|
|
|
|
/// clang has trouble instantiating them (with std=c++1z) resulting in
|
|
|
|
/// undefined symbols at link time. By using a simple struct and inheriting
|
|
|
|
/// from it we can avoid this problem. Revist this solution at clang-4.0.
|
|
|
|
|
|
|
|
#define INTROSPECTION_ENUM_DECL(NS,E, ...) \
|
|
|
|
namespace util { \
|
|
|
|
struct PASTE(__enum_traits_,E) { \
|
|
|
|
using value_type = ::NS::E; \
|
|
|
|
\
|
|
|
|
static constexpr \
|
|
|
|
size_t value_count = VA_ARGS_COUNT(__VA_ARGS__); \
|
|
|
|
\
|
|
|
|
static const \
|
|
|
|
std::array<value_type,value_count> \
|
|
|
|
values; \
|
|
|
|
\
|
|
|
|
static const \
|
|
|
|
std::array<const char*,value_count> \
|
|
|
|
names; \
|
|
|
|
}; \
|
|
|
|
\
|
|
|
|
template <> \
|
|
|
|
struct enum_traits<::NS::E> : public PASTE(__enum_traits_,E) \
|
|
|
|
{ }; \
|
|
|
|
\
|
|
|
|
template <> \
|
|
|
|
struct type_name<::NS::E> { \
|
|
|
|
static constexpr const char ns[] = #NS; \
|
|
|
|
static constexpr const char value[] = #E; \
|
|
|
|
}; \
|
|
|
|
} \
|
2016-03-11 19:16:35 +11:00
|
|
|
|
|
|
|
|
|
|
|
///------------------------------------------------------------------------
|
|
|
|
/// Declares specialisations for introspection data structures for an
|
|
|
|
/// enum E, in namespace NS, with variadic values __VA_ARGS__.
|
|
|
|
///
|
2016-12-21 16:46:16 +11:00
|
|
|
/// Expects to be called from outside all namespaces.
|
2016-03-11 19:16:35 +11:00
|
|
|
#define INTROSPECTION_ENUM_IMPL(NS,E, ...) \
|
|
|
|
constexpr \
|
|
|
|
std::array< \
|
2016-03-14 22:34:14 +11:00
|
|
|
util::enum_traits<::NS::E>::value_type, \
|
|
|
|
util::enum_traits<::NS::E>::value_count \
|
2016-12-21 16:46:16 +11:00
|
|
|
> PASTE(util::__enum_traits_,E)::values = { \
|
|
|
|
MAP1(NAMESPACE_LIST, ::NS::E, __VA_ARGS__) \
|
|
|
|
}; \
|
2016-03-11 19:16:35 +11:00
|
|
|
\
|
2016-12-21 16:46:16 +11:00
|
|
|
const \
|
2016-03-11 19:16:35 +11:00
|
|
|
std::array< \
|
|
|
|
const char*, \
|
2016-03-14 22:34:14 +11:00
|
|
|
util::enum_traits<::NS::E>::value_count \
|
2016-12-21 16:46:16 +11:00
|
|
|
> PASTE(util::__enum_traits_,E)::names = { \
|
|
|
|
MAP(STRINGIZE_LIST, __VA_ARGS__) \
|
|
|
|
}; \
|
2016-03-11 19:16:35 +11:00
|
|
|
\
|
|
|
|
constexpr \
|
2016-07-28 13:45:09 +10:00
|
|
|
const char util::type_name<::NS::E>::ns[]; \
|
2016-03-14 22:34:14 +11:00
|
|
|
\
|
|
|
|
constexpr \
|
2016-07-28 13:45:09 +10:00
|
|
|
const char util::type_name<::NS::E>::value[]; \
|
2016-03-11 19:16:35 +11:00
|
|
|
|
|
|
|
|
|
|
|
///------------------------------------------------------------------------
|
|
|
|
/// Defines an istream extraction operator for an enumeration E, within
|
|
|
|
/// namespace NS
|
|
|
|
///
|
|
|
|
/// Expects to be called from outside all namespaces.
|
|
|
|
///
|
|
|
|
/// The user is responsible for specialising the ::util::enum_traits<NS::E>
|
|
|
|
/// parameters which are used to drive the implementation (eg, through
|
|
|
|
/// INTROSPECTION_ENUM_DECL, and INTROSPECTION_ENUM_IMPL).
|
|
|
|
///
|
|
|
|
/// For trivial enumerations INTROSPECTION_ENUM may be easier to use.
|
|
|
|
|
|
|
|
#define INTROSPECTION_ENUM_ISTREAM(NS,E) \
|
|
|
|
std::istream& \
|
2016-03-14 22:34:14 +11:00
|
|
|
::NS::operator>> (std::istream &is, ::NS::E &e) \
|
2016-03-11 19:16:35 +11:00
|
|
|
{ \
|
2016-03-14 22:34:14 +11:00
|
|
|
using traits = util::enum_traits<::NS::E>; \
|
2016-03-11 19:16:35 +11:00
|
|
|
\
|
|
|
|
std::string name; \
|
|
|
|
is >> name; \
|
|
|
|
\
|
|
|
|
std::transform (std::begin (name), \
|
|
|
|
std::end (name), \
|
|
|
|
std::begin (name), \
|
|
|
|
::toupper); \
|
|
|
|
\
|
|
|
|
auto name_pos = std::find ( \
|
|
|
|
std::cbegin (traits::names), \
|
|
|
|
std::cend (traits::names), \
|
|
|
|
name \
|
|
|
|
); \
|
|
|
|
\
|
|
|
|
if (name_pos == std::cend (traits::names)) { \
|
|
|
|
is.setstate (std::istream::failbit); \
|
|
|
|
} else { \
|
|
|
|
auto d = std::distance ( \
|
|
|
|
std::begin (traits::names), \
|
|
|
|
name_pos \
|
|
|
|
); \
|
|
|
|
\
|
|
|
|
e = traits::values[d]; \
|
|
|
|
} \
|
|
|
|
\
|
|
|
|
return is; \
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Defines an ostream insertion operator for an enumeration E, within
|
|
|
|
/// namespace NS.
|
|
|
|
///
|
|
|
|
/// Expects to be called from outside all namespaces.
|
|
|
|
///
|
|
|
|
/// The user is responsible for specialising the ::util::enum_traits<NS::E>
|
|
|
|
/// parameters which are used to drive the implementation (eg, through
|
|
|
|
/// INTROSPECTION_ENUM_DECL, and INTROSPECTION_ENUM_IMPL).
|
|
|
|
///
|
|
|
|
/// For trivial enumerations INTROSPECTION_ENUM may be easier to use.
|
|
|
|
#define INTROSPECTION_ENUM_OSTREAM(NS,E) \
|
|
|
|
std::ostream& \
|
2016-03-14 22:34:14 +11:00
|
|
|
::NS::operator<< (std::ostream &os, ::NS::E e) \
|
2016-03-11 19:16:35 +11:00
|
|
|
{ \
|
2016-03-14 22:34:14 +11:00
|
|
|
using traits = ::util::enum_traits<::NS::E>; \
|
2016-03-11 19:16:35 +11:00
|
|
|
\
|
|
|
|
auto val_pos = std::find ( \
|
|
|
|
std::cbegin (traits::values), \
|
|
|
|
std::cend (traits::values), \
|
|
|
|
e \
|
|
|
|
); \
|
|
|
|
\
|
|
|
|
if (val_pos == std::cend (traits::values)) { \
|
|
|
|
os.setstate (std::ostream::failbit); \
|
|
|
|
} else { \
|
|
|
|
auto d = std::distance ( \
|
|
|
|
std::cbegin (traits::values), \
|
|
|
|
val_pos \
|
|
|
|
); \
|
|
|
|
\
|
|
|
|
os << traits::names[d]; \
|
|
|
|
} \
|
|
|
|
\
|
|
|
|
return os; \
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Defines an enum, its values, associated introspection structures, and
|
|
|
|
/// istream and ostream operators.
|
2016-12-21 16:46:16 +11:00
|
|
|
///
|
2016-03-11 19:16:35 +11:00
|
|
|
/// This must be called from outside all namespaces as
|
|
|
|
/// INTROSPECTION_ENUM_DECL and INTROSPECTION_ENUM_IMPL need to declare
|
|
|
|
/// and define structures outside the user's namespace.
|
|
|
|
///
|
|
|
|
/// The enum will be defined inside an inline namespace to simplify the
|
|
|
|
/// passing of parameters to functions which require some namespace
|
|
|
|
/// prefixing. This shouldn't have a practical effect on user code.
|
|
|
|
|
|
|
|
#define INTROSPECTION_ENUM(E, ...) \
|
|
|
|
inline namespace detail_intr_enum { \
|
|
|
|
enum E { __VA_ARGS__ }; \
|
|
|
|
std::ostream& operator<< (std::ostream&, E); \
|
|
|
|
std::istream& operator>> (std::istream&, E&); \
|
|
|
|
} \
|
|
|
|
INTROSPECTION_ENUM_DECL(detail_intr_enum,E,__VA_ARGS__) \
|
|
|
|
INTROSPECTION_ENUM_IMPL(detail_intr_enum,E,__VA_ARGS__) \
|
|
|
|
INTROSPECTION_ENUM_ISTREAM(detail_intr_enum,E) \
|
|
|
|
INTROSPECTION_ENUM_OSTREAM(detail_intr_enum,E)
|
|
|
|
|
|
|
|
|
2016-03-14 22:34:14 +11:00
|
|
|
#define INTROSPECTION_ENUM_CLASS(E, ...) \
|
|
|
|
inline namespace detail_intr_enum { \
|
|
|
|
enum class E { __VA_ARGS__ }; \
|
|
|
|
std::ostream& operator<< (std::ostream&, E); \
|
|
|
|
std::istream& operator>> (std::istream&, E&); \
|
|
|
|
} \
|
|
|
|
INTROSPECTION_ENUM_DECL(detail_intr_enum,E,__VA_ARGS__) \
|
|
|
|
INTROSPECTION_ENUM_IMPL(detail_intr_enum,E,__VA_ARGS__) \
|
|
|
|
INTROSPECTION_ENUM_ISTREAM(detail_intr_enum,E) \
|
|
|
|
INTROSPECTION_ENUM_OSTREAM(detail_intr_enum,E)
|
|
|
|
|
|
|
|
|
2015-04-20 17:51:00 +10:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Describes a single member variable in a type availabe for introspection
|
|
|
|
///
|
|
|
|
/// K: target class
|
|
|
|
/// R: member type
|
|
|
|
/// M: pointer-to-member
|
|
|
|
template <
|
|
|
|
class K,
|
|
|
|
typename R,
|
|
|
|
R K::*M
|
|
|
|
>
|
|
|
|
struct field
|
|
|
|
{
|
|
|
|
typedef K klass;
|
|
|
|
typedef R type;
|
|
|
|
|
|
|
|
static const std::string name;
|
2016-03-17 18:05:28 +11:00
|
|
|
|
2015-04-20 17:51:00 +10:00
|
|
|
static const R& get (const K &k) { return k.*M; }
|
2016-03-17 18:05:28 +11:00
|
|
|
static R& get ( K &k) { return k.*M; }
|
|
|
|
static R& get ( K &&) = delete;
|
2015-04-20 17:51:00 +10:00
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/// Holds the fields of a type available for introspection
|
|
|
|
///
|
|
|
|
/// Specialise the following type struct with a 'fields' tuple of the
|
|
|
|
/// members that should be accessed like below:
|
|
|
|
///
|
|
|
|
/// struct foo { int a; int b; };
|
|
|
|
///
|
|
|
|
/// template <> struct type<foo>
|
|
|
|
/// {
|
|
|
|
/// typedef std::tuple<
|
|
|
|
/// field<foo,int,&foo::a>,
|
|
|
|
/// field<foo,int,&foo::b>
|
|
|
|
/// > fields;
|
|
|
|
/// };
|
2016-12-21 16:46:16 +11:00
|
|
|
///
|
2015-04-20 17:51:00 +10:00
|
|
|
/// template <> const std::string field<foo,int,&foo::a>::name = "a";
|
|
|
|
/// template <> const std::string field<foo,int,&foo::b>::name = "b";
|
|
|
|
|
|
|
|
template <class K>
|
|
|
|
struct type { };
|
|
|
|
|
2015-05-01 02:49:19 +10:00
|
|
|
|
2015-04-20 17:51:00 +10:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/// traits class which converts an introspected type to a tuple
|
|
|
|
///
|
|
|
|
/// K: target class
|
2015-05-01 02:49:19 +10:00
|
|
|
|
|
|
|
template <typename K>
|
|
|
|
struct type_tuple;
|
|
|
|
|
|
|
|
template <
|
|
|
|
typename ...T
|
|
|
|
> struct type_tuple<
|
|
|
|
std::tuple<T...>
|
|
|
|
> {
|
|
|
|
typedef std::tuple<T...> type;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2015-04-20 17:51:00 +10:00
|
|
|
template <
|
|
|
|
typename K,
|
|
|
|
typename I = typename make_indices<
|
|
|
|
std::tuple_size<
|
|
|
|
typename type<K>::fields
|
|
|
|
>::value
|
|
|
|
>::type
|
2015-05-01 02:49:19 +10:00
|
|
|
> struct _type_tuple;
|
2015-04-20 17:51:00 +10:00
|
|
|
|
|
|
|
template <
|
|
|
|
typename K,
|
|
|
|
size_t ...I
|
2015-05-01 02:49:19 +10:00
|
|
|
> struct _type_tuple <
|
2015-04-20 17:51:00 +10:00
|
|
|
K,
|
|
|
|
indices<I...>
|
2015-05-01 02:49:19 +10:00
|
|
|
> {
|
2015-04-20 17:51:00 +10:00
|
|
|
typedef std::tuple<
|
|
|
|
typename std::tuple_element<
|
|
|
|
I,
|
|
|
|
typename type<K>::fields
|
|
|
|
>::type::type...
|
|
|
|
> type;
|
|
|
|
};
|
|
|
|
|
2015-05-01 02:49:19 +10:00
|
|
|
|
|
|
|
template <
|
|
|
|
typename K
|
|
|
|
> struct type_tuple {
|
|
|
|
typedef typename _type_tuple<K>::type type;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2015-04-20 17:51:00 +10:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace detail {
|
|
|
|
template <
|
|
|
|
typename K,
|
|
|
|
typename I = typename make_indices<
|
|
|
|
std::tuple_size<
|
|
|
|
typename type<K>::fields
|
|
|
|
>::value
|
|
|
|
>::type
|
|
|
|
>
|
|
|
|
struct _as_tuple;
|
|
|
|
|
|
|
|
template <
|
|
|
|
typename K,
|
|
|
|
size_t ...I
|
|
|
|
>
|
|
|
|
struct _as_tuple <
|
|
|
|
K,
|
|
|
|
indices<I...>
|
|
|
|
>
|
|
|
|
{
|
|
|
|
static typename type_tuple<K>::type
|
|
|
|
make (const K &k)
|
|
|
|
{
|
|
|
|
return std::make_tuple (
|
|
|
|
std::tuple_element<I, typename type<K>::fields>::type::get (k)...
|
|
|
|
);
|
|
|
|
}
|
2016-03-17 18:05:28 +11:00
|
|
|
|
|
|
|
static auto make (K&&) = delete;
|
2015-04-20 17:51:00 +10:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convert an introspection capable class instance into a tuple instance
|
|
|
|
///
|
|
|
|
/// K: source class
|
|
|
|
template <typename K>
|
|
|
|
auto
|
|
|
|
as_tuple (const K &k)
|
|
|
|
{
|
|
|
|
return detail::_as_tuple<K>::make (k);
|
|
|
|
}
|
2016-03-17 18:05:28 +11:00
|
|
|
|
|
|
|
template <typename K>
|
|
|
|
auto as_tuple (K &_k)
|
|
|
|
{
|
|
|
|
const K &k = _k;
|
|
|
|
return as_tuple (k);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename K>
|
|
|
|
auto as_tuple (K&&) = delete;
|
2015-04-20 17:51:00 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|