Created
October 23, 2015 13:27
-
-
Save LucHermitte/032b669e1bc9fb486cea to your computer and use it in GitHub Desktop.
Playing with opaque typedefs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A little attempt at defining a way to define opaque typedefs | |
// Yeah, it has already been done | |
// - https://www.youtube.com/watch?v=jLdSjh8oqmE | |
// - http://sourceforge.net/projects/opaque_typedef/ | |
// (c) 2015 Luc Hermitte | |
// Distributed under the Boost Software License, Version 1.0. | |
// See accompanying file LICENSE_1_0.txt or copy at | |
// http://www.boost.org/LICENSE_1_0.txt | |
// TODO | |
// - add some missing noexcept specs | |
// - add missing constexpr | |
// - Have a way to tell that the new int based type is not addable for instance; i.e. a way to fine tune the operations supported. | |
#include <ostream> | |
#include <type_traits> | |
#include <boost/operators.hpp> | |
// Helper types: à la enable_if -> if_then_else | |
template <bool V, typename IfTrueType, typename IfFalseType> | |
struct if_then_else_c | |
{ using type = IfTrueType; }; | |
template <typename IfTrueType, typename IfFalseType> | |
struct if_then_else_c<false,IfTrueType,IfFalseType> | |
{ using type = IfFalseType; }; | |
template <typename Cond, typename IfTrueType, typename IfFalseType> | |
struct if_then_else : if_then_else_c<Cond::value, IfTrueType, IfFalseType> {}; | |
// Holder for non class types | |
template <typename T, typename Tag> struct holder_type | |
{ | |
// Conversions opaque <- T | |
holder_type(T const& v_) | |
noexcept(std::is_nothrow_copy_constructible<T>::value) | |
: v(v_) {} | |
holder_type(T && v_) | |
noexcept | |
: v(std::move(v_)) {} | |
holder_type operator=(T const& v_) | |
noexcept(std::is_nothrow_assignable<T,T>::value) | |
{ v = v_; return *this; } | |
holder_type operator=(T && v_) | |
noexcept(std::is_nothrow_assignable<T,T>::value) | |
{ v = std::move(v_); return *this; } | |
// Provide stream insertion and extraction | |
friend std::ostream & operator<<(std::ostream & os, const holder_type<T, Tag> & o) | |
{ return os << o.v; } | |
friend std::istream & operator<<(std::istream & is, const holder_type<T, Tag> & o) | |
{ return is >> o.v; } | |
// Conversions T <- opaque | |
explicit operator T const& () const noexcept { return v; } | |
// Forward support for arithmetic binary operations | |
holder_type & operator+=(holder_type const& rhs_) { | |
v += rhs_.v; | |
return *this; | |
} | |
holder_type & operator-=(holder_type const& rhs_) { | |
v -= rhs_.v; | |
return *this; | |
} | |
holder_type & operator*=(holder_type const& rhs_) { | |
v *= rhs_.v; | |
return *this; | |
} | |
holder_type & operator/=(holder_type const& rhs_) { | |
v /= rhs_.v; | |
return *this; | |
} | |
private: | |
T v; | |
}; | |
// The opaque type | |
template <typename T, typename Tag> | |
struct opaque | |
: if_then_else<std::is_class<T>, T, holder_type<T, Tag>>::type | |
, private boost::operators<opaque<T,Tag>> | |
{ | |
using super = typename if_then_else<std::is_class<T>, T, holder_type<T, Tag>>::type; | |
using super::super; | |
using super::operator=; | |
template <typename U, typename G> opaque(opaque<U,G> const&) = delete; | |
template <typename U, typename G> opaque(opaque<U,G> &&) = delete; | |
template <typename U, typename G> opaque& operator=(opaque<U,G> const&) = delete; | |
template <typename U, typename G> opaque& operator=(opaque<U,G> &&) = delete; | |
}; | |
#define OPAQUE_TYPEDEF(OpaqueType, Type) \ | |
struct OpaqueType : opaque<Type,OpaqueType> { \ | |
using super = opaque<Type,OpaqueType>; \ | |
using super::super; \ | |
using super::operator=; \ | |
}; | |
// Test with int (integral and scalar) | |
static_assert(!std::is_class<int>::value, "un int n'est pas un object"); | |
#include <iostream> | |
namespace test_int1 { | |
// using => poor error messages | |
struct X_ {}; | |
struct Y_ {}; | |
using X = opaque<int, X_>; | |
using Y = opaque<int, Y_>; | |
static_assert(!std::is_convertible<X,Y>::value, "Ne doivent pas être convertibles"); | |
void f(X x, Y y) { | |
std::cout << "x: " << x << "\ty:" << y << "\n"; | |
} | |
void test() | |
{ | |
auto x = X { 10 }; | |
auto y = Y { 20 }; | |
f(x,y); | |
X x2 = x; (void)x2; | |
// f(y,x); // <- Error | |
// y = x; // <- Error | |
// Y yy = x; // <- Error | |
std::cout << x + x << "\n"; | |
// std::cout << x + y << "\n"; // <- Error | |
} | |
} // test_int1 namespace | |
namespace test_int2 { | |
// Template code hidden behind structs => better error messages | |
OPAQUE_TYPEDEF(X, int); | |
OPAQUE_TYPEDEF(Y, int); | |
static_assert(!std::is_convertible<X,Y>::value, "Ne doivent pas être convertibles"); | |
void f(X x, Y y) { | |
std::cout << "x: " << x << "\ty:" << y << "\n"; | |
} | |
void test() | |
{ | |
auto x = X { 10 }; | |
auto y = Y { 20 }; | |
f(x,y); | |
X x2 = x; (void)x2; | |
// f(y,x); // <- Error | |
// y = x; // <- Error | |
// Y yy = x; // <- Error | |
std::cout << x + x << "\n"; | |
// std::cout << x + y << "\n"; // <- Error | |
} | |
} // test_int1 namespace | |
// Test with strings (a standard type that support + | |
#include <string> | |
#include <list> | |
void test_string() | |
{ | |
static_assert(std::is_class<std::string>::value, "une string n'est pas un object"); | |
struct Str_{}; | |
using string = opaque<std::string, Str_>; | |
struct Str2_{}; | |
using string2 = opaque<std::string, Str2_>; | |
static_assert(std::is_base_of<std::string, string>::value, "On hérite!"); | |
auto s = string{"toto"}; | |
s += "titi"; | |
std::cout << s << "\n"; | |
std::cout << "cstr" << static_cast<std::string>(s).c_str() << "\n"; | |
std::cout << "cstr" << s.c_str() << "\n"; | |
std::cout << "s+s" << s+s << "\n"; | |
auto s2 = string2{"S2"}; | |
// s = s2; // <- Error | |
// s2 = s; // <- Error | |
// std::cout << s + s2 << "\n"; // <- Error | |
} | |
// Test with a user-type that support += | |
struct MyNumber { | |
MyNumber(double d_ = 0.0) : d(d_) {} | |
friend std::ostream & operator<<(std::ostream & os, const MyNumber & v) | |
{ return os << v.d; } | |
MyNumber & operator+=(MyNumber const& n_) { | |
d += n_.d; | |
return *this; | |
} | |
friend MyNumber operator+(MyNumber lhs_, MyNumber const& rhs_) | |
{ return lhs_+=rhs_;} | |
private: | |
double d; | |
}; | |
void test_mynumber() | |
{ | |
struct X_{}; | |
struct Y_{}; | |
using X = opaque<MyNumber, X_>; | |
using Y = opaque<MyNumber, Y_>; | |
auto x = X{1.0}; | |
auto y = Y{2.0}; (void)y; | |
std::cout << x+x << "\n"; | |
// std::cout << x/x << "\n"; // <- Error | |
// std::cout << x+y << "\n"; // <- Error | |
} | |
// Test with a User Type that don't support += | |
struct MyNonMath { | |
MyNonMath(double d_ = 0.0) : d(d_) {} | |
friend std::ostream & operator<<(std::ostream & os, const MyNonMath & v) | |
{ return os << v.d; } | |
private: | |
double d; | |
}; | |
void test_myNonMathType() | |
{ | |
struct X_{}; | |
struct Y_{}; | |
using X = opaque<MyNonMath, X_>; | |
using Y = opaque<MyNonMath, Y_>; | |
auto x = X{1.0}; | |
auto y = Y{2.0}; (void)y; | |
std::cout << x << "-" << y << "\n"; | |
// std::cout << x+x << "\n"; // <- Error | |
// std::cout << x+y << "\n"; // <- Error | |
} | |
int main () | |
{ | |
test_int1::test(); | |
test_int2::test(); | |
test_string(); | |
test_mynumber(); | |
test_myNonMathType(); | |
} | |
// Vim: let $CXXFLAGS='-std=c++11 -Wall -Wno-c++98-compat -Wno-missing-prototypes' | |
// Vim: let $CXXFLAGS='-std=c++14 -Wall -Weverything -Wno-c++98-compat -Wno-missing-prototypes -stdlib=libc++' | |
// Vim: let $CXXFLAGS='-std=c++14 -Wall -Weverything -Wno-c++98-compat -Wno-missing-prototypes' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment