Skip to content

Instantly share code, notes, and snippets.

@LucHermitte
Created October 23, 2015 13:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LucHermitte/032b669e1bc9fb486cea to your computer and use it in GitHub Desktop.
Save LucHermitte/032b669e1bc9fb486cea to your computer and use it in GitHub Desktop.
Playing with opaque typedefs
// 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