Skip to content

Instantly share code, notes, and snippets.

@nicuveo
Last active August 29, 2015 14:16
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 nicuveo/f054bb25e0b953784cb6 to your computer and use it in GitHub Desktop.
Save nicuveo/f054bb25e0b953784cb6 to your computer and use it in GitHub Desktop.
Extending library visitor for user-defined types with macros

Summary

The code in this gist tries to find a way to have pseudo "multi methods" in C++ by extending a visitor interface, hence the title: extending library visitor for user-defined types with macros. The idea is this: you want to implement the Visitor pattern over a class hierarchy defined in a library. But users of the library might have their own classes, extending the hierarchy, forcing your visitor to know about them. My proposal: a small macros that extend the visitor with the needed methods.

The issue, however, is that this solution tends to silently corrupt the vtables in the executable... This gist documents my trials to find a solution that yields the correct code in both g++ and clang, that does compile with their respective flags activated, and that does NOT depend on the order of declarations (see EXTEND_AFTER).

  • g++ flag: -fno-weak
  • clang++ flag: -Wweak-vtables

You can run ./test.sh to see the result of different strategies. "OK" means it compiled and worked as expected, "CE" means it didn't compile, and "KO" means it did compile without any warning but generated incorrect code.

Strategies

  • TEMPLATE_VISITOR: makes visitor a template class to prevent weak vtables warnings.
  • EXTEND_AFTER: forces additional methods to be added at the end of the interface.
  • TEMPLATE_APPLY: apply is made via a unique template static method.
  • INLINE_APPLY: apply is inlined.
  • EXTERN_APPLY: if template, instantiation is limited to client.

Result

Yay!

g++ -fno-weak -DTEMPLATE_APPLY -DEXTERN_APPLY                                              OK
g++ -fno-weak -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY                           OK
g++ -fno-weak -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTERN_APPLY                               OK
g++ -fno-weak -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY            OK
clang++ -Wweak-vtables -DTEMPLATE_APPLY -DTEMPLATE_VISITOR                                 OK
clang++ -Wweak-vtables -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY                  OK
clang++ -Wweak-vtables -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR                  OK
clang++ -Wweak-vtables -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY   OK

Which means that the TEMPLATE_APPLY, TEMPLATE_VISITOR, and EXTERN_APPLY are enough to get the desired behavior.

// includes
#include <iostream>
// custom user type predeclaration
#define USER_TYPES (DerivedC)
class DerivedC;
#include "library.hh"
// custom user type
class DerivedC : public Base
{
public:
void apply(const Visitor& v) const;
};
void DerivedC::apply(const Visitor& v) const
{
v(*this);
}
// custom visitor
class PrintVisitor : public Visitor
{
public:
void operator()(const DerivedA&) const;
void operator()(const DerivedB&) const;
void operator()(const DerivedC&) const;
};
void PrintVisitor::operator()(const DerivedA&) const { std::cout << "A"; }
void PrintVisitor::operator()(const DerivedB&) const { std::cout << "B"; }
void PrintVisitor::operator()(const DerivedC&) const { std::cout << "C"; }
// main
int main()
{
PrintVisitor v;
DerivedA a;
DerivedB b;
DerivedC c;
Base const* pa = &a;
Base const* pb = &b;
Base const* pc = &c;
pa->apply(v);
pb->apply(v);
pc->apply(v);
std::cout << std::endl;
}
#! /usr/bin/env bash
echo "### from library POV"
"$@" -E library.cc | egrep -v "^#|^$" | sed '0,/class Base/d'
echo
echo
echo
echo
echo "### from client POV"
"$@" -E client.cc | egrep -v "^#|^$" | sed '0,/class Base/d'
// includes
#define IS_LIB
#include "library.hh"
// constructors (and thus vtables)
Base::Base()
{
}
Base::~Base()
{
}
DerivedA::DerivedA()
{
}
DerivedA::~DerivedA()
{
}
DerivedB::DerivedB()
{
}
DerivedB::~DerivedB()
{
}
// apply out of line definition
#if !defined(INLINE_APPLY)
void DerivedA::apply(const Visitor& v) const
{
DECLARE_APPLY;
};
void DerivedB::apply(const Visitor& v) const
{
DECLARE_APPLY;
};
#endif
// includes
#include <boost/preprocessor.hpp>
// custom user type for visitor extension
#ifndef USER_TYPES
# define USER_TYPES
#endif
#define USER_TYPE_DECL(R, D, TYPE) \
virtual void operator() (TYPE const&) const = 0;
// predeclarations
class Base;
class DerivedA;
class DerivedB;
// (extended) visitor interface
#ifdef TEMPLATE_VISITOR
template <typename T>
class TemplateVisitor;
typedef TemplateVisitor<void> Visitor;
template <typename T>
class TemplateVisitor
#else
class Visitor
#endif
{
public:
#ifndef EXTEND_AFTER
BOOST_PP_SEQ_FOR_EACH(USER_TYPE_DECL, _, USER_TYPES)
#endif
virtual void operator() (DerivedA const&) const = 0;
virtual void operator() (DerivedB const&) const = 0;
#ifdef EXTEND_AFTER
BOOST_PP_SEQ_FOR_EACH(USER_TYPE_DECL, _, USER_TYPES)
#endif
};
// class hierarchy
class Base
{
public:
Base();
virtual ~Base();
virtual void apply(const Visitor& v) const = 0;
#ifdef TEMPLATE_APPLY
protected:
template <typename T>
static void do_apply(const T& that, const Visitor& v);
# define DECLARE_APPLY do_apply(*this, v)
#else
# define DECLARE_APPLY v(*this)
#endif
};
class DerivedA : public Base
{
public:
DerivedA();
virtual ~DerivedA();
virtual void apply(const Visitor& v) const;
};
class DerivedB : public Base
{
public:
DerivedB();
virtual ~DerivedB();
virtual void apply(const Visitor& v) const;
};
// apply strategies
#if defined(INLINE_APPLY)
inline void DerivedA::apply(const Visitor& v) const
{
DECLARE_APPLY;
};
inline void DerivedB::apply(const Visitor& v) const
{
DECLARE_APPLY;
};
#endif
#if defined(TEMPLATE_APPLY)
# if defined(EXTERN_APPLY) && defined(IS_LIB)
extern template void Base::do_apply<DerivedA>(const DerivedA&, const Visitor&);
extern template void Base::do_apply<DerivedB>(const DerivedB&, const Visitor&);
# else
template <typename T>
inline void Base::do_apply(const T& that, const Visitor& v)
{
v(that);
}
template void Base::do_apply<DerivedA>(const DerivedA&, const Visitor&);
template void Base::do_apply<DerivedB>(const DerivedB&, const Visitor&);
# endif
#endif
CC = g++
CCEXE = $(CC) -Wall -Wextra -Werror
client: library.a client.o
$(CCEXE) client.o -L. -lrary -o $@
client.o: client.cc library.hh Makefile
$(CCEXE) -c $< -o $@
library.o: library.cc library.hh Makefile
$(CCEXE) -c $< -o $@
library.a: library.o
ar -scr $@ $<
clean:
rm -fv client library.a library.o client.o
#! /usr/bin/env bash
function test_run()
{
local cc="$1"
local out=""
make clean
make CC="$cc" || return 1
out=$(./client)
make clean
if [ "$out" == "ABC" ] ; then
return 0
else
return 2
fi
}
function test_report()
{
local cc="$1"
local res="$2"
local ok=""
local so=""
local ko=""
local no=""
local msg=""
if [ -t 1 ] && tput "colors" &> /dev/null ; then
ok="$(tput bold)$(tput setaf 2)"
so="$(tput bold)$(tput setaf 3)"
ko="$(tput bold)$(tput setaf 1)"
no="$(tput sgr0)"
fi
printf "%-110s " "$cc"
if [ $res -eq 0 ] ; then
printf "%s%s%s\n" "$ok" "OK" "$no"
elif [ $res -eq 1 ] ; then
printf "%s%s%s\n" "$so" "CE" "$no"
else
printf "%s%s%s\n" "$ko" "KO" "$no"
fi
}
function test()
{
local cc="$1"
local res=""
test_run "$cc" 2>&1 &> /dev/null
res=$?
test_report "$cc" "$res"
}
test_cc()
{
which "$1" &> /dev/null
res=$?
if [ $res -ne 0 ] ; then
echo "$1 not found" >&2
fi
return $res
}
if test_cc "g++" ; then
test "g++"
test "g++ -DEXTEND_AFTER"
test "g++ -DTEMPLATE_VISITOR"
test "g++ -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "g++ -DINLINE_APPLY"
test "g++ -DINLINE_APPLY -DEXTEND_AFTER"
test "g++ -DINLINE_APPLY -DTEMPLATE_VISITOR"
test "g++ -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "g++ -DTEMPLATE_APPLY"
test "g++ -DTEMPLATE_APPLY -DEXTEND_AFTER"
test "g++ -DTEMPLATE_APPLY -DEXTERN_APPLY"
test "g++ -DTEMPLATE_APPLY -DEXTERN_APPLY -DEXTEND_AFTER"
test "g++ -DTEMPLATE_APPLY -DTEMPLATE_VISITOR"
test "g++ -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "g++ -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY"
test "g++ -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY -DEXTEND_AFTER"
test "g++ -DTEMPLATE_APPLY -DINLINE_APPLY"
test "g++ -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTEND_AFTER"
test "g++ -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTERN_APPLY"
test "g++ -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTERN_APPLY -DEXTEND_AFTER"
test "g++ -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR"
test "g++ -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "g++ -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY"
test "g++ -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY -DEXTEND_AFTER"
test "g++ -fno-weak"
test "g++ -fno-weak -DEXTEND_AFTER"
test "g++ -fno-weak -DTEMPLATE_VISITOR"
test "g++ -fno-weak -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "g++ -fno-weak -DINLINE_APPLY"
test "g++ -fno-weak -DINLINE_APPLY -DEXTEND_AFTER"
test "g++ -fno-weak -DINLINE_APPLY -DTEMPLATE_VISITOR"
test "g++ -fno-weak -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "g++ -fno-weak -DTEMPLATE_APPLY"
test "g++ -fno-weak -DTEMPLATE_APPLY -DEXTEND_AFTER"
test "g++ -fno-weak -DTEMPLATE_APPLY -DEXTERN_APPLY"
test "g++ -fno-weak -DTEMPLATE_APPLY -DEXTERN_APPLY -DEXTEND_AFTER"
test "g++ -fno-weak -DTEMPLATE_APPLY -DTEMPLATE_VISITOR"
test "g++ -fno-weak -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "g++ -fno-weak -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY"
test "g++ -fno-weak -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY -DEXTEND_AFTER"
test "g++ -fno-weak -DTEMPLATE_APPLY -DINLINE_APPLY"
test "g++ -fno-weak -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTEND_AFTER"
test "g++ -fno-weak -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTERN_APPLY"
test "g++ -fno-weak -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTERN_APPLY -DEXTEND_AFTER"
test "g++ -fno-weak -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR"
test "g++ -fno-weak -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "g++ -fno-weak -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY"
test "g++ -fno-weak -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY -DEXTEND_AFTER"
fi
if test_cc "clang++" ; then
test "clang++"
test "clang++ -DEXTEND_AFTER"
test "clang++ -DTEMPLATE_VISITOR"
test "clang++ -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "clang++ -DINLINE_APPLY"
test "clang++ -DINLINE_APPLY -DEXTEND_AFTER"
test "clang++ -DINLINE_APPLY -DTEMPLATE_VISITOR"
test "clang++ -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "clang++ -DTEMPLATE_APPLY"
test "clang++ -DTEMPLATE_APPLY -DEXTEND_AFTER"
test "clang++ -DTEMPLATE_APPLY -DEXTERN_APPLY"
test "clang++ -DTEMPLATE_APPLY -DEXTERN_APPLY -DEXTEND_AFTER"
test "clang++ -DTEMPLATE_APPLY -DTEMPLATE_VISITOR"
test "clang++ -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "clang++ -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY"
test "clang++ -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY -DEXTEND_AFTER"
test "clang++ -DTEMPLATE_APPLY -DINLINE_APPLY"
test "clang++ -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTEND_AFTER"
test "clang++ -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTERN_APPLY"
test "clang++ -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTERN_APPLY -DEXTEND_AFTER"
test "clang++ -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR"
test "clang++ -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "clang++ -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY"
test "clang++ -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY -DEXTEND_AFTER"
test "clang++ -Wweak-vtables"
test "clang++ -Wweak-vtables -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DTEMPLATE_VISITOR"
test "clang++ -Wweak-vtables -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DINLINE_APPLY"
test "clang++ -Wweak-vtables -DINLINE_APPLY -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DINLINE_APPLY -DTEMPLATE_VISITOR"
test "clang++ -Wweak-vtables -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DEXTERN_APPLY"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DEXTERN_APPLY -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DTEMPLATE_VISITOR"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DINLINE_APPLY"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTERN_APPLY"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DINLINE_APPLY -DEXTERN_APPLY -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTEND_AFTER"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY"
test "clang++ -Wweak-vtables -DTEMPLATE_APPLY -DINLINE_APPLY -DTEMPLATE_VISITOR -DEXTERN_APPLY -DEXTEND_AFTER"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment