Is it possible to detect the following assignment at compile time? - c++

So I have the following case, apologies for the long example, but it should compile correctly:
#include <tuple>
#include <functional>
#include <iostream>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/push_front.hpp>
#include <boost/mpl/vector.hpp>
namespace mpl = boost::mpl;
namespace aux
{
template <typename ...Args>
struct to_vector
{ };
template <typename T, typename ...Args>
struct to_vector<T, Args...>
{ typedef typename mpl::push_front<typename to_vector<Args...>::type, T>::type type; };
template <typename T>
struct to_vector<T>
{ typedef typename mpl::vector<T> type; };
template <>
struct to_vector<>
{ typedef typename mpl::vector<> type; };
template <typename Dest, typename T>
struct tuple_adder
{
typedef decltype(std::tuple_cat(std::declval<Dest>(), std::make_tuple(std::declval<T>()))) type;
};
}
struct foo
{
struct storage
{ };
template <typename T>
struct placeholder : storage
{
placeholder(T&& t) : content(t)
{ }
T content;
};
storage* data;
template <typename ...Args>
foo(Args&&... args)
: data()
{
typedef typename mpl::fold<
typename aux::to_vector<Args...>::type,
std::tuple<>,
aux::tuple_adder<mpl::_1, mpl::_2>
>::type tuple_type;
// Instantiate the tuple
data = new placeholder<tuple_type>(std::make_tuple(std::forward<Args>(args)...));
}
template <typename ...Args>
void set(Args&&... args)
{
typedef typename mpl::fold<
typename aux::to_vector<Args...>::type,
std::tuple<>,
aux::tuple_adder<mpl::_1, mpl::_2>
>::type tuple_type;
auto tp = static_cast<placeholder<tuple_type>*>(data);
*tp = std::make_tuple(std::forward<Args>(args)...);
}
};
int main()
{
foo f(1, 2., std::string("Hello"));
f.set(4, 3., std::string("Bar"));
f.set(3., std::string("Bar"), 3.);
}
The idea is simple, foo utilises type erasure to store a tuple that is constructed via the constructor. Then the restriction should be that set is only allowable where the tuple generated from the variadic argument list matches the held tuple (which has unfortunately had it's type erased.)
Now I can detect this at runtime using typeid(), however, I'd like to know if there is a way I can have the same detection at compile time. The only constraints are that foo cannot be templated, and variant is out as I want to allow foo to be constructed with the fields as necessary (all specified at compile time...)
I fear the answer is that this is not possible (due to the type erasure), however I'm hoping for some ideas of a way to achieve this functionality...

The point of a compile time type system is that it constrains the allowable operations on values of types. If two objects are the same type then they admit the same allowable operations.
So no, there's no way for the compiler to know what you want to allow, given that you've erased the distinction.

Related

Typetrait to get value type of contiguous-memory containers

I need a typetrait that would return value type for the following "contiguous-memory" types (to which operator[] can be applied):
std::vector<T, Args...>, std::array<T, N>, T[], T[N], and T* (with possibly CV qualifiers like T* const). The trait should return T for all above types.
Is there a more concise implementation in C++17, than the tedious one below? Like somehow gathering all pointer cases in one specialization and appliyng std::remove_ptr, same for T[N] with std::remove_extent.
template<class T> struct remove_array_like;
template<class T> struct remove_array_like<T*> { using type = T; };
template<class T> struct remove_array_like<T* const> { using type = T; };
template<class T> struct remove_array_like<T* volatile> { using type = T; };
template<class T> struct remove_array_like<T* const volatile> { using type = T; };
template<class T> struct remove_array_like<T[]> { using type = T; };
template<class T, std::size_t N>
struct remove_array_like<T[N]> { using type = T; };
template<class T, std::size_t N>
struct remove_array_like<std::array<T, N>> { using type = T; };
template<class T, class... Args>
struct remove_array_like<std::vector<T, Args...>> { using type = T; };
I need a typetrait that would return value type for the following "contiguous-memory" types (to which operator[] can be applied):
To "extract" the required type, it seems to me that you can simply use the operator[] itself, removing references, volatiles and consts. See the following helper struct
template <typename T>
struct contained_type
{ using type = std::remove_cv_t<
std::remove_reference_t<
decltype(std::declval<T>()[0])>>; };
You can write your contained_type struct using SFINAE, so with an additional defaulted template parameter
template <typename, typename = void>
struct remove_array_like;
It seems to me that you needs only three specializations.
One for contigous-memory containers (so for containers with a data() method that return a pointer to the start of the contigous contained memory), that is for std::vector, std::array, std::string and other strings types
template <typename T>
struct remove_array_like<T, std::void_t<decltype(std::declval<T>().data())>>
: public contained_type<T>
{ };
one for pointers
template <typename T>
struct remove_array_like<T, std::enable_if_t<std::is_pointer_v<T>>>
: public contained_type<T>
{ };
and one for arrays
template <typename T>
struct remove_array_like<T, std::enable_if_t<std::is_array_v<T>>>
: public contained_type<T>
{ };
The following is a full compiling C++17 example
#include <set>
#include <array>
#include <deque>
#include <vector>
#include <string>
#include <type_traits>
template <typename T>
struct contained_type
{ using type = std::remove_cv_t<
std::remove_reference_t<
decltype(std::declval<T>()[0])>>; };
template <typename, typename = void>
struct remove_array_like;
template <typename T>
struct remove_array_like<T, std::void_t<decltype(std::declval<T>().data())>>
: public contained_type<T>
{ };
template <typename T>
struct remove_array_like<T, std::enable_if_t<std::is_pointer_v<T>>>
: public contained_type<T>
{ };
template <typename T>
struct remove_array_like<T, std::enable_if_t<std::is_array_v<T>>>
: public contained_type<T>
{ };
int main ()
{
using T1 = remove_array_like<std::vector<int>>::type;
//using T2 = remove_array_like<std::deque<int>>::type; // error! no data(), no contigous
using T3 = remove_array_like<std::array<int, 1u>>::type;
using T4 = remove_array_like<std::string>::type;
using T5 = remove_array_like<int * volatile>::type;
using T6 = remove_array_like<int const [1u]>::type;
using T7 = remove_array_like<int volatile []>::type;
// using T8 = remove_array_like<std::set<int>>::type; // error!
// using T9 = remove_array_like<int>::type; // error!
static_assert( std::is_same_v<T1, int> );
static_assert( std::is_same_v<T3, int> );
static_assert( std::is_same_v<T4, char> );
static_assert( std::is_same_v<T5, int> );
static_assert( std::is_same_v<T6, int> );
static_assert( std::is_same_v<T7, int> );
}
Starting from C++20 you can use std::remove_cvref, so contained_type can be simplified as follows
template <typename T>
struct contained_type
{ using type = std::remove_cvref_t<
decltype(std::declval<T>()[0])>; };

Declaring a function with types of arguments taken from a foreign parameter pack

Suppose I have some kind of type list
template<typename... Types> struct TypeList {};
Now in some other class I can generate such a TypeList in a variety of ways.
template<class T> struct MyClass {
using MyList = TypeList<T, typename Something<T>::type, SomethingElse>;
// ...
};
How can I declare a method with types of arguments extracted from this type list? For example, if I set MyList = TypeList<int, float, const char*>, I wish a method
void my_method(int, float, const char*)
to be declared.
You could derive from a base class that implements the method:
template <typename> struct MethodProvider;
template <typename ...Args>
struct MethodProvider<TypeList<Args...>>
{
void my_method(Args ...args);
};
template <typename T>
struct MyClassAux
{
using MyList = TypeList<T, typename Something<T>::type, SomethingElse>;
};
template <typename T>
struct MyClass
: private MyClassAux<T>
, private MethodProvider<typename MyClassAux<T>::MyList>
{
using typename MyClassAux<T>::MyList;
using MethodProvider<typename MyClassAux<T>::MyList>::my_method;
// ...
};
You can use static_assert and std::is_same to constraint the arguments of my_method
#include <iostream>
#include <string>
#include <vector>
#include <type_traits>
template<typename... Types> struct TypeList {};
template<class T> struct MyClass {
using MyList = TypeList<int, T>;
template<typename ...Args>
void my_method(Args ...args) {
static_assert(std::is_same<MyList, TypeList<Args...>>::value, "invalid arguments");
auto dummy = {(std::cout << args << "\n", 0)...};
(void)(dummy); // avoid variable warning
}
};
int main()
{
MyClass<float> c;
c.my_method(1, 2.3f);
// c.my_method(1, ""); // unmatched arguments won't compile
// c.my_method(1);
}
Online Demo

SFINAE not happening with std::underlying_type

Below SFINAE code with variadic templates compiles nicely using clang 3.7.1, C++14:
#include <array>
#include <iostream>
#include <vector>
#include <cstdint>
enum class Bar : uint8_t {
ay, bee, see
};
struct S {
static void foo() {}
// std::begin(h) is defined for h of type H
template<typename H, typename... T>
static typename std::enable_if<std::is_pointer<decltype(std::begin(std::declval<H>()))*>::value>::type
foo(const H&, T&&... t)
{ std::cout << "container\n"; foo(std::forward<T>(t)...); }
// H is integral
template<typename H, typename... T>
static typename std::enable_if<std::is_integral<typename std::remove_reference<H>::type>::value>::type
foo(const H&, T&&... t)
{ std::cout << "integer\n"; foo(std::forward<T>(t)...); }
// H is an enum with underlying type = uint8_t
/*
template<typename H, typename... T>
static typename std::enable_if<std::is_same<typename std::underlying_type<H>::type,uint8_t>::value>::type
foo(const H&, T&&... t)
{ std::cout << "enum\n"; foo(std::forward<T>(t)...); }
*/
};
int main()
{
S::foo(std::array<int,8>(), 5, 5L, std::vector<int>{}, 5L);
}
I want the correct overload of foo to be called recursively, based on the type H:
if std::begin(h) is defined for an h of type H, I want the
overload number 1 to be chosen
if H is an "integral type", I want overload number 2.
This works as it is. But if I add another overload for enum types (you can try to un-comment the third overload), then I get:
error: only enumeration types have underlying types
I agree that only enums got an underlying type, hence why is Not the third overload (with std::underlying_type) get SFINAE-d away?
std::underlying_type is not SFINAE friendly. Attempting to access std::underlying_type<T>::type for a non-enumeration type results in undefined behavior (often a hard error), not substitution failure.
You need to ascertain that the type at issue is an enumeration type first, before attempting to access its underlying type. Writing this in line would be something along the lines of typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type. Replacing the typename std::underlying_type<H>::type in your return type with this hideous mess and you get an even more hideous mess that works :)
If you find yourself needing to do this often - or just don't want to write typename std::enable_if<std::is_same<typename std::enable_if<std::is_enum<H>::value, std::underlying_type<H>>::type::type, uint8_t>::value>::type - you can write a SFINAE-friendly underlying_type:
template<class T, bool = std::is_enum<T>::value>
struct safe_underlying_type : std::underlying_type<T> {};
template<class T>
struct safe_underlying_type<T, false /* is_enum */> {};
Here's a solution inspired from T.C.'s solution that worked for my use case:
template <typename T, bool = std::is_enum<T>::value>
struct relaxed_underlying_type {
using type = typename std::underlying_type<T>::type;
};
template <typename T>
struct relaxed_underlying_type<T, false> {
using type = T;
};
Example Usage:
template <typename T>
struct UnwrapEnum {
using type =
typename std::conditional<
std::is_enum<T>::value,
typename relaxed_underlying_type<T>::type,
T>
::type;
};
enum class MyEnum : int {};
class MyClass {};
int main() {
UnwrapEnum<MyEnum>::type x;
static_assert(std::is_same<decltype(x), int>::value);
UnwrapEnum<MyClass>::type y;
static_assert(std::is_same<decltype(y), MyClass>::value);
return 0;
}

Get twice as wide type

basically, i'd like to (at compile time) get the twice-as-wide type from a stdint type. I can do it by hand like this
template <typename T>
class twice_as_wide{};
template<>
class twice_as_wide<uint8_t>
{
public:
typedef uint16_t type;
};
template<>
class twice_as_wide<int8_t>
{
public:
typedef int16_t type;
};
template<>
class twice_as_wide<uint16_t>
{
public:
typedef uint32_t type;
};
ect, I just want to make sure that this doesn't exist yet. I'm using visual studio 2010 C++0X (annoying, i know) and already have a boost dependency. Does anyone know of an existing implementation of this?
If you don't mind another boost dependency, then you could do this:
#include <type_traits>
#include <boost/integer.hpp>
template <typename T, bool is_unsigned = std::is_unsigned<T>::value>
struct twice_as_wide
{
typedef typename boost::uint_t< 2 * std::numeric_limits<T>::digits>::exact type;
};
template<typename T>
struct twice_as_wide<T, false>
{
typedef typename boost::int_t< 2 * (std::numeric_limits<T>::digits + 1)>::exact type;
};
template< typename T>
using twice_as_wide_t = typename twice_as_wide<T>::type;
I'd say, use Boost Integer. Demo that keeps signed-ness of the source type: Live on Coliru
#include <boost/integer.hpp>
#include <limits>
namespace helpers
{
// wrappers around Boost Integer http://www.boost.org/doc/libs/1_54_0/libs/integer/doc/html/boost_integer/integer.html#boost_integer.integer.sized
template <bool is_signed, int bin_digits> struct select_twice;
template <int bin_digits> struct select_twice<true, bin_digits> {
using type = typename boost::int_t<bin_digits + 1>::least;
};
template <int bin_digits> struct select_twice<false, bin_digits> {
using type = typename boost::uint_t<bin_digits>::least;
};
}
template <typename Int>
using select_twice = helpers::select_twice<std::numeric_limits<Int>::is_signed, std::numeric_limits<Int>::digits*2>;
template <typename Int>
using twice_t = typename select_twice<Int>::type;
int main()
{
static_assert(std::is_same<uint16_t, twice_t<uint8_t>>::value, "oops");
static_assert(std::is_same<uint32_t, twice_t<uint16_t>>::value, "oops");
static_assert(std::is_same<uint64_t, twice_t<uint32_t>>::value, "oops");
}

standard c++11 way to remove all pointers of a type

Is there a way to do this with some c++11 or at most a boost library?
#include <iostream>
#include <typeinfo>
using namespace std;
template <typename T> class remove_all_pointers{
public:
typedef T type;
};
template <typename T> class remove_all_pointers<T*>{
public:
typedef typename remove_all_pointers<T>::type type;
};
int main(){
//correctly prints 'i' on gcc
cout<<typeid(remove_all_pointers<int****>::type).name()<<endl;
}
That doesn't quite work for all pointer types. You need to account for different cv-qualifiers as well:
template <typename T> class remove_all_pointers<T* const>{
public:
typedef typename remove_all_pointers<T>::type type;
};
template <typename T> class remove_all_pointers<T* volatile>{
public:
typedef typename remove_all_pointers<T>::type type;
};
template <typename T> class remove_all_pointers<T* const volatile >{
public:
typedef typename remove_all_pointers<T>::type type;
};
Since C++17 you can create a readable, simple and cv-qualifier aware meta function.
Use it like:
int main()
{
remove_all_pointers_t<int* const* volatile* const volatile*> v = 42;
return 0;
}
C++20
#include <type_traits>
template<typename T>
struct remove_all_pointers : std::conditional_t<
std::is_pointer_v<T>,
remove_all_pointers<
std::remove_pointer_t<T>
>,
std::type_identity<T>
>
{};
template<typename T>
using remove_all_pointers_t = typename remove_all_pointers<T>::type;
C++17
In C++17 std::type_identity isn't available yet and std::identity isn't available anymore, hence you need to create your own 'identity' meta function:
#include <type_traits>
// your custom 'identity' meta function
template <typename T>
struct identity
{
using type = T;
};
template<typename T>
struct remove_all_pointers : std::conditional_t<
std::is_pointer_v<T>,
remove_all_pointers<
std::remove_pointer_t<T>
>,
identity<T>
>
{};
template<typename T>
using remove_all_pointers_t = typename remove_all_pointers<T>::type;
Neither Boost nor C++11 features such a trait template. But your code should work.