Giter Site home page Giter Site logo

jll63 / yomm2 Goto Github PK

View Code? Open in Web Editor NEW
325.0 14.0 17.0 6.55 MB

Fast, orthogonal, open multi-methods. Solve the Expression Problem in C++17.

License: Boost Software License 1.0

CMake 5.07% C++ 88.35% Batchfile 0.03% Shell 0.56% Dockerfile 0.07% Python 5.93%
cpp17 multi-methods multiple-dispatch open-methods polymorphism cpp expression-problem

yomm2's Introduction

YOMM2

This library implements fast, open, multi-methods for C++17. It is strongly inspired by the papers by Peter Pirkelbauer, Yuriy Solodkyy, and Bjarne Stroustrup.

TL;DR

If you are familiar with the concept of open multi-methods, or if you prefer to learn by reading code, go directly to the synopsis. The reference is here

Open Methods in a Nutshell

Cross-cutting Concerns and the Expression Problem

You have a matrix math library. It deals with all sort of matrices: dense, diagonal, tri-diagonal, etc. Each matrix subtype has a corresponding class in a hierarchy rooted in Matrix.

Now you would like to render Matrix objects as JSON strings. The representation will vary depending on the exact type of the object; for example, if a matrix is a DiagonalMatrix, you only need to store the diagonal - the other elements are all zeroes.

This is an example of a "cross-cutting concern". How do you do it?

It turns out that OOP doesn't offer a good solution to this.

You can stick a pure virtual to_json function in the Matrix base class and override it in the subclasses. It is an easy solution but it has severe drawbacks. It requires you to change the Matrix class and its subclasses, and recompile the library. And now all the applications that use it will contain the to_json functions even if they don't need them, because of the way virtual functions are implemented.

Or you may resort on a "type switch": have the application test for each category and generate the JSON accordingly. This is tedious, error prone and, above all, not extensible. Adding a new matrix subclass requires updating all the type switches. The Visitor pattern also suffers from this flaw.

Wouldn't it be nice if you could add behavior to existing types, just as easily and unintrusively as you can extend existing class hierarchies via derivation? What if you could solve the so-called Expression Problem:

existing behaviors += new types
existing types += new behaviors

This is exactly what Open Methods are all about: solving the Expression Problem.

Let's look at an example.

// -----------------------------------------------------------------------------
// library code

struct matrix {
    virtual ~matrix() {
    }
    // ...
};

struct dense_matrix : matrix { /* ... */
};
struct diagonal_matrix : matrix { /* ... */
};

// -----------------------------------------------------------------------------
// application code

#include <memory>
#include <yorel/yomm2/keywords.hpp>

register_classes(matrix, dense_matrix, diagonal_matrix);

declare_method(std::string, to_json, (virtual_<const matrix&>));

define_method(std::string, to_json, (const dense_matrix& m)) {
    return "json for dense matrix...";
}

define_method(std::string, to_json, (const diagonal_matrix& m)) {
    return "json for diagonal matrix...";
}

int main() {
    yorel::yomm2::update();

    const matrix& a = dense_matrix();
    const matrix& b = diagonal_matrix();

    std::cout << to_json(a) << "\n"; // json for dense matrix
    std::cout << to_json(b) << "\n"; // json for diagonal matrix

    return 0;
}

<yorel/yomm2/keywords.hpp> is the library's main entry point. It declares a set of macros, and injects a single name, virtual_, in the global namespace. The purpose of the header is to make it look as if open methods are part of the language.

register_classes informs the library of the existence of the classes, and their inheritance relationships. Any class that can appear in a method call needs to be registered, even if it is not directly referenced by a method.

declare_method declares an open method called to_json, which takes one virtual argument of type const matrix& and returns a std::string. The virtual_<> decorator specifies that the argument must be taken into account to select the appropriate specialization. In essence, this is the same thing as having a virtual std::string to_json() const inside class Matrix - except that the virtual function lives outside of any classes, and you can add as many as you want without changing the classes. NOTE: DO NOT specify argument names, i.e. virtual_<const matrix&> arg is not permitted.

define_method defines two implementations for the to_json method: one for dense matrices, and one for diagonal matrices.

yorel::yomm2::update() creates the dispatch tables; it must be called before any method is called, and after dynamically loading and unloading shared libraries.

The example can be compiled (from the root of the repository) with:

clang++- -I include -std=c++17 tutorials/README.cpp -o example

Multiple Dispatch

Methods can have more than one virtual argument. This is handy in certain situations, for example to implement binary operations on matrices:

// -----------------------------------------------------------------------------
// matrix * matrix

declare_method(
    std::shared_ptr<const matrix>,
    times, (virtual_<const matrix&>, virtual_<const matrix&>));

// catch-all matrix * matrix -> dense_matrix
define_method(
    std::shared_ptr<const matrix>,
    times, (const matrix& a, const matrix& b)) {
    return std::make_shared<dense_matrix>();
}

// diagonal_matrix * diagonal_matrix -> diagonal_matrix
define_method(
    std::shared_ptr<const matrix>,
    times, (const diagonal_matrix& a, const diagonal_matrix& b)) {
    return std::make_shared<diagonal_matrix>();
}

Performance

Open methods are almost as fast as ordinary virtual member functions once you turn on optimization (-O2). With both clang and gcc, dispatching a call to a method with one virtual argument takes 15-30% more time than calling the equivalent virtual member function (unless the call goes through a virtual base, which requires a dynamic cast). It does not involve branching or looping, only a few memory reads (which the CPU can be parallelize), a multiplication, a bit shift, a final memory read, then an indirect call. If the body of the method does any amount of work, the difference is unnoticeable.

virtual_ptr, a fat pointer class, can be used to make method dispatch even faster - three instructions and two memory reads -, without sacrificing orthogonality.

Examples are available on Compiler Explorer.

Building and Installing

Make sure that you have the following dependencies:

  • a C++17 capable compiler

  • cmake version 3.20 or above

Clone the repository:

git clone https://github.com/jll63/yomm2.git
cd yomm2

Create a build directory and run cmake then make:

mkdir build
cd build
cmake ..
make

If you want to run the tests, specify it when running cmake:

cmake .. -DYOMM2_ENABLE_TESTS=1
make && ctest

YOMM2 uses several Boost libraries:

  1. Preprocessor, DynamicBitset, TypeTraits: included by YOMM2 headers

  2. Boost.Test: only used to run the test suite

If these libraries are already available on your machine, and they can be found by cmake, they will be used. In this case, make sure that the pre-installed libraries are at version 1.74 or above. If Boost is not found, the latest version will be downloaded, and the Boost headers mentioned in section (1) will be installed along YOMM2 (if you decide to make install).

If you also want to run the benchmarks (and in this case you really want a release build):

cmake .. -DYOMM2_ENABLE_TESTS=1 -DYOMM2_ENABLE_BENCHMARKS=1 -DCMAKE_BUILD_TYPE=Release
make && tests/benchmarks

This will automatically download the dependency benchmark, build it and finally install it to ./extern within the root directory of yomm2.

If you like YOMM2, and you want to install it:

# either:
sudo make install
# or:
make install DESTDIR=/path/to/my/libs

This will install the headers and a CMake package configuration. By default, YOMM2 is installed as a headers only library. The examples can be compiled like this (after installation):

clang++ -std=c++17 -O3 examples/synopsis.cpp -o synopsis

Or directly from the repository (i.e. without installing):

clang++ -std=c++17 -O3 -Iinclude examples/synopsis.cpp -o synopsis

The YOMM2 runtime - responsible for building the dispatch tables - adds ~75K to the image, or ~64K after stripping.

The runtime can also be built and installed as a shared library, by adding -DYOMM2_SHARED=1 to the cmake command line.

A CMake package configuration is also installed. If the install location is in CMAKE_PREFIX_PATH, you can use find_package(YOMM2) to locate YOMM2, then target_link_libraries(<your_target> YOMM2::yomm2) to add the necessary include paths and the library. See this example.

Make sure to add the install location to CMAKE_PREFIX_PATH so that you can use find_package(YOMM2) from your including project. For linking, the use target_link_library(<your_target> YOMM2::yomm2). This will automatically add the necessary include directories, so this should be all you need to do to link to yomm2.

Going Further

The Reference is here. Since version 1.3.0, some of the internals are documented, which make it possible to use the library without using macros - see the API tutorial.

YOMM2 has experimental support for writing templatized methods and definitions - see the templates tutorial.

The library comes with a series of examples:

I presented the library at CppCon 2018. Here are the video recording and the slides.

Roadmap

YOMM2 has been stable (in the sense of being backward-compatible) for many years, but it is still evolving. Here are the items on which I intend to work in the future. No promises, no time table.

  • Dispatch on std::any and std::variant.
  • Tunable runtime.
  • Static linking of dispatch data.
  • Minimal perfect hash tables as an option.
  • Multi-threaded hash search.
  • Make error handler a std::function.
  • Get closer to Stroustrup et al's papers (version 2.0):
    • use compatible return types for disambiguation
    • move support for std::shared_ptr and unique_ptr to an optional header

If you have ideas, comments, suggestions...get in touch! If you use YOMM2, I would appreciate it if you take the time to send me a description of your use case(s), and links to the project(s), if they are publicly available.

yomm2's People

Contributors

cloudhan avatar derpda avatar fabienpean avatar ibob avatar jll63 avatar larkwiot avatar rasie1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yomm2's Issues

Boost 1.53 compatibility

First off, I realize that yomm2 dependencies clearly state that Boost 1.65 or later is recommended.
However, the supercomputers which our library is intended to be used on have older versions installed, specifically 1.54 on TSUBAME and 1.53 on ABCI.
Supercomputers having old library versions installed is a sad fact of the field.

Poblem

The issue is, Boost 1.53/1.54 do not have boost/preprocessor/tuple/push_front.hpp yet. It was added 1.56 (ouch, so close...).

That header only contains the following definition

# include <boost/preprocessor/array/push_front.hpp>
# include <boost/preprocessor/array/to_tuple.hpp>
# include <boost/preprocessor/tuple/to_array.hpp>

# define BOOST_PP_TUPLE_PUSH_FRONT(tuple, elem) \
    BOOST_PP_ARRAY_TO_TUPLE(BOOST_PP_ARRAY_PUSH_FRONT(BOOST_PP_TUPLE_TO_ARRAY(tuple), elem)) \
/**/

This is a simple convenience routine transforming the tuple into an array, using array's push_front to add an element, and finally transforming the resulting array back into a tuple.
If I simply replace BOOST_PP_TUPLE_PUSH_FRONT with its definition and include the additional headers, I can compile yomm2 with the old boost version without issues.

Suggestion

Use new header if available (check Boost version). If not available, define the needed function.

#if Boost_VERSION_MINOR >= 56
#include <boost/preprocessor/tuple/push_front.hpp>
#else
#include <boost/preprocessor/array/push_front.hpp>
#include <boost/preprocessor/array/to_tuple.hpp>
#include <boost/preprocessor/tuple/to_array.hpp>
#define BOOST_PP_TUPLE_PUSH_FRONT(tuple, elem) BOOST_PP_ARRAY_TO_TUPLE(BOOST_PP_ARRAY_PUSH_FRONT(BOOST_PP_TUPLE_TO_ARRAY(tuple), elem))
#endif

I realize this is somewhat ugly and might not be something you want to include, but it would help greatly in my case since compiling a newer boost version as a non-root user is significant effort. We don't want to force users of our library on supercomputers to build boost first and fiddle around with paths.
Moving back to yomm11 is an option for us, but we would like to avoid that.

Segfault during benchmark [v1.1.2]

Hi there,
I cloned v1.1.2 today and got a segfault while running the benchmarks.
Below are the shell commands and output which was used; if there's anything else I can provide then please let me know, thanks.

Setup:

$ cd /tmp
$ git clone --branch v1.1.2 https://github.com/jll63/yomm2
$ cd yomm2
$ git switch -c v1.1.2
$ mkdir build && cd build

Generate:

$ cmake .. -DYOMM2_ENABLE_TESTS=1 -DYOMM2_ENABLE_BENCHMARKS=1 -DCMAKE_BUILD_TYPE=Release
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Using Boost libraries from /usr/include
-- Tests enabled
-- Benchmarks enabled
-- Package "benchmark" not found in system.
-- Downloading dependency "benchmark" and building from source.
Scanning dependencies of target benchmark
[ 12%] Creating directories for 'benchmark'
[ 25%] Performing download step (git clone) for 'benchmark'
Cloning into 'benchmark'...
remote: Enumerating objects: 3, done.        
remote: Counting objects: 100% (3/3), done.        
remote: Compressing objects: 100% (3/3), done.        
remote: Total 5757 (delta 0), reused 0 (delta 0), pack-reused 5754        
Receiving objects: 100% (5757/5757), 1.83 MiB | 2.94 MiB/s, done.
Resolving deltas: 100% (3771/3771), done.
Already on 'master'
Your branch is up-to-date with 'origin/master'.
[ 37%] No patch step for 'benchmark'
[ 50%] Performing update step for 'benchmark'
Current branch master is up to date.
[ 62%] Performing configure step for 'benchmark'
-- The CXX compiler identification is GNU 9.3.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Failed to find LLVM FileCheck
-- Found Git: /usr/bin/git (found version "2.25.1") 
-- git Version: v1.5.2-ea5a5bbf
-- Version: 1.5.2
-- Performing Test HAVE_CXX_FLAG_STD_CXX11
-- Performing Test HAVE_CXX_FLAG_STD_CXX11 - Success
-- Performing Test HAVE_CXX_FLAG_WALL
-- Performing Test HAVE_CXX_FLAG_WALL - Success
-- Performing Test HAVE_CXX_FLAG_WEXTRA
-- Performing Test HAVE_CXX_FLAG_WEXTRA - Success
-- Performing Test HAVE_CXX_FLAG_WSHADOW
-- Performing Test HAVE_CXX_FLAG_WSHADOW - Success
-- Performing Test HAVE_CXX_FLAG_WERROR
-- Performing Test HAVE_CXX_FLAG_WERROR - Success
-- Performing Test HAVE_CXX_FLAG_WSHORTEN_64_TO_32
-- Performing Test HAVE_CXX_FLAG_WSHORTEN_64_TO_32 - Failed
-- Performing Test HAVE_CXX_FLAG_FSTRICT_ALIASING
-- Performing Test HAVE_CXX_FLAG_FSTRICT_ALIASING - Success
-- Performing Test HAVE_CXX_FLAG_WNO_DEPRECATED_DECLARATIONS
-- Performing Test HAVE_CXX_FLAG_WNO_DEPRECATED_DECLARATIONS - Success
-- Performing Test HAVE_CXX_FLAG_WNO_DEPRECATED
-- Performing Test HAVE_CXX_FLAG_WNO_DEPRECATED - Success
-- Performing Test HAVE_CXX_FLAG_WSTRICT_ALIASING
-- Performing Test HAVE_CXX_FLAG_WSTRICT_ALIASING - Success
-- Performing Test HAVE_CXX_FLAG_WD654
-- Performing Test HAVE_CXX_FLAG_WD654 - Failed
-- Performing Test HAVE_CXX_FLAG_WTHREAD_SAFETY
-- Performing Test HAVE_CXX_FLAG_WTHREAD_SAFETY - Failed
-- Performing Test HAVE_CXX_FLAG_COVERAGE
-- Performing Test HAVE_CXX_FLAG_COVERAGE - Success
-- Performing Test HAVE_STD_REGEX
-- Performing Test HAVE_STD_REGEX
-- Performing Test HAVE_STD_REGEX -- success
-- Performing Test HAVE_GNU_POSIX_REGEX
-- Performing Test HAVE_GNU_POSIX_REGEX
-- Performing Test HAVE_GNU_POSIX_REGEX -- failed to compile
-- Performing Test HAVE_POSIX_REGEX
-- Performing Test HAVE_POSIX_REGEX
-- Performing Test HAVE_POSIX_REGEX -- success
-- Performing Test HAVE_STEADY_CLOCK
-- Performing Test HAVE_STEADY_CLOCK
-- Performing Test HAVE_STEADY_CLOCK -- success
-- Looking for C++ include pthread.h
-- Looking for C++ include pthread.h - found
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Failed
-- Check if compiler accepts -pthread
-- Check if compiler accepts -pthread - yes
-- Found Threads: TRUE  
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/yomm2/build/benchmark_build
[ 75%] Performing build step for 'benchmark'
Scanning dependencies of target benchmark
[  4%] Building CXX object src/CMakeFiles/benchmark.dir/benchmark.cc.o
[  9%] Building CXX object src/CMakeFiles/benchmark.dir/benchmark_api_internal.cc.o
[ 14%] Building CXX object src/CMakeFiles/benchmark.dir/benchmark_name.cc.o
[ 19%] Building CXX object src/CMakeFiles/benchmark.dir/benchmark_register.cc.o
[ 23%] Building CXX object src/CMakeFiles/benchmark.dir/benchmark_runner.cc.o
[ 28%] Building CXX object src/CMakeFiles/benchmark.dir/colorprint.cc.o
[ 33%] Building CXX object src/CMakeFiles/benchmark.dir/commandlineflags.cc.o
[ 38%] Building CXX object src/CMakeFiles/benchmark.dir/complexity.cc.o
[ 42%] Building CXX object src/CMakeFiles/benchmark.dir/console_reporter.cc.o
[ 47%] Building CXX object src/CMakeFiles/benchmark.dir/counter.cc.o
[ 52%] Building CXX object src/CMakeFiles/benchmark.dir/csv_reporter.cc.o
[ 57%] Building CXX object src/CMakeFiles/benchmark.dir/json_reporter.cc.o
[ 61%] Building CXX object src/CMakeFiles/benchmark.dir/reporter.cc.o
[ 66%] Building CXX object src/CMakeFiles/benchmark.dir/sleep.cc.o
[ 71%] Building CXX object src/CMakeFiles/benchmark.dir/statistics.cc.o
[ 76%] Building CXX object src/CMakeFiles/benchmark.dir/string_util.cc.o
[ 80%] Building CXX object src/CMakeFiles/benchmark.dir/sysinfo.cc.o
[ 85%] Building CXX object src/CMakeFiles/benchmark.dir/timers.cc.o
[ 90%] Linking CXX static library libbenchmark.a
[ 90%] Built target benchmark
Scanning dependencies of target benchmark_main
[ 95%] Building CXX object src/CMakeFiles/benchmark_main.dir/benchmark_main.cc.o
[100%] Linking CXX static library libbenchmark_main.a
[100%] Built target benchmark_main
[ 87%] Performing install step for 'benchmark'
[ 90%] Built target benchmark
[100%] Built target benchmark_main
Install the project...
-- Install configuration: "Release"
-- Installing: /tmp/yomm2/dependencies/benchmark/lib/libbenchmark.a
-- Installing: /tmp/yomm2/dependencies/benchmark/lib/libbenchmark_main.a
-- Installing: /tmp/yomm2/dependencies/benchmark/lib/cmake/benchmark/benchmarkConfig.cmake
-- Installing: /tmp/yomm2/dependencies/benchmark/lib/cmake/benchmark/benchmarkConfigVersion.cmake
-- Installing: /tmp/yomm2/dependencies/benchmark/lib/pkgconfig/benchmark.pc
-- Installing: /tmp/yomm2/dependencies/benchmark/lib/cmake/benchmark/benchmarkTargets.cmake
-- Installing: /tmp/yomm2/dependencies/benchmark/lib/cmake/benchmark/benchmarkTargets-release.cmake
[100%] Completed 'benchmark'
[100%] Built target benchmark
-- Examples enabled
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/yomm2/build

Build:

$ make
Scanning dependencies of target yomm2
[  2%] Building CXX object src/CMakeFiles/yomm2.dir/yomm2.cpp.o
[  5%] Linking CXX static library libyomm2.a
[  5%] Built target yomm2
Scanning dependencies of target benchmarks
[  8%] Building CXX object tests/CMakeFiles/benchmarks.dir/benchmarks.cpp.o
[ 10%] Building CXX object tests/CMakeFiles/benchmarks.dir/benchmarks_vfuncs.cpp.o
[ 13%] Linking CXX executable benchmarks
[ 13%] Built target benchmarks
Scanning dependencies of target namespaces
[ 16%] Building CXX object tests/CMakeFiles/namespaces.dir/namespaces.cpp.o
[ 18%] Linking CXX executable namespaces
[ 18%] Built target namespaces
Scanning dependencies of target lab
[ 21%] Building CXX object tests/CMakeFiles/lab.dir/lab.cpp.o
[ 24%] Linking CXX executable lab
[ 24%] Built target lab
Scanning dependencies of target blackbox
[ 27%] Building CXX object tests/CMakeFiles/blackbox.dir/blackbox.cpp.o
[ 29%] Linking CXX executable blackbox
[ 29%] Built target blackbox
Scanning dependencies of target whitebox
[ 32%] Building CXX object tests/CMakeFiles/whitebox.dir/whitebox.cpp.o
[ 35%] Linking CXX executable whitebox
[ 35%] Built target whitebox
Scanning dependencies of target synopsis
[ 37%] Building CXX object examples/CMakeFiles/synopsis.dir/synopsis.cpp.o
[ 40%] Linking CXX executable synopsis
[ 40%] Built target synopsis
Scanning dependencies of target matrix
[ 43%] Building CXX object examples/CMakeFiles/matrix.dir/matrix.cpp.o
[ 45%] Linking CXX executable matrix
[ 45%] Built target matrix
Scanning dependencies of target next
[ 48%] Building CXX object examples/CMakeFiles/next.dir/next.cpp.o
[ 51%] Linking CXX executable next
[ 51%] Built target next
Scanning dependencies of target asteroids
[ 54%] Building CXX object examples/CMakeFiles/asteroids.dir/asteroids.cpp.o
[ 56%] Linking CXX executable asteroids
[ 56%] Built target asteroids
Scanning dependencies of target accept_no_visitors
[ 59%] Building CXX object examples/CMakeFiles/accept_no_visitors.dir/accept_no_visitors.cpp.o
[ 62%] Linking CXX executable accept_no_visitors
[ 62%] Built target accept_no_visitors
Scanning dependencies of target adventure
[ 64%] Building CXX object examples/CMakeFiles/adventure.dir/adventure.cpp.o
[ 67%] Linking CXX executable adventure
[ 67%] Built target adventure
Scanning dependencies of target dl_shared
[ 70%] Building CXX object examples/CMakeFiles/dl_shared.dir/dl_shared.cpp.o
[ 72%] Linking CXX shared library libdl_shared.so
[ 72%] Built target dl_shared
Scanning dependencies of target dl_main
[ 75%] Building CXX object examples/CMakeFiles/dl_main.dir/dl_main.cpp.o
[ 78%] Linking CXX executable dl_main
[ 78%] Built target dl_main
Scanning dependencies of target containers
[ 81%] Building CXX object examples/containers/CMakeFiles/containers.dir/main.cpp.o
[ 83%] Building CXX object examples/containers/CMakeFiles/containers.dir/shape_painter.cpp.o
[ 86%] Building CXX object examples/containers/CMakeFiles/containers.dir/concrete_shape_painters.cpp.o
[ 89%] Building CXX object examples/containers/CMakeFiles/containers.dir/line_painter.cpp.o
[ 91%] Building CXX object examples/containers/CMakeFiles/containers.dir/arc_painter.cpp.o
[ 94%] Building CXX object examples/containers/CMakeFiles/containers.dir/segment_painter.cpp.o
[ 97%] Building CXX object examples/containers/CMakeFiles/containers.dir/painter.cpp.o
[100%] Linking CXX executable containers
[100%] Built target containers

Test:

$ ctest
Test project /tmp/yomm2/build
      Start  1: whitebox
 1/11 Test  #1: whitebox .........................   Passed    0.00 sec
      Start  2: blackbox
 2/11 Test  #2: blackbox .........................   Passed    0.00 sec
      Start  3: lab
 3/11 Test  #3: lab ..............................   Passed    0.00 sec
      Start  4: namespaces
 4/11 Test  #4: namespaces .......................   Passed    0.00 sec
      Start  5: synopsis
 5/11 Test  #5: synopsis .........................   Passed    0.00 sec
      Start  6: matrix
 6/11 Test  #6: matrix ...........................   Passed    0.00 sec
      Start  7: accept_no_visitors
 7/11 Test  #7: accept_no_visitors ...............   Passed    0.00 sec
      Start  8: adventure
 8/11 Test  #8: adventure ........................   Passed    0.00 sec
      Start  9: next
 9/11 Test  #9: next .............................   Passed    0.00 sec
      Start 10: asteroids
10/11 Test #10: asteroids ........................   Passed    0.00 sec
      Start 11: containers
11/11 Test #11: containers .......................   Passed    0.00 sec

100% tests passed, 0 tests failed out of 11

Total Test time (real) =   0.03 sec

Benchmark:

$ tests/benchmarks 
2021-02-13T12:16:31+00:00
Running tests/benchmarks
Run on (8 X 4000 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x4)
  L1 Instruction 32 KiB (x4)
  L2 Unified 256 KiB (x4)
  L3 Unified 8192 KiB (x1)
Load Average: 1.88, 1.68, 1.47
***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.
----------------------------------------------------------------------------------
Benchmark                                        Time             CPU   Iterations
----------------------------------------------------------------------------------
virtual function call                         2.21 ns         2.19 ns    309250625
uni-method call                               2.89 ns         2.86 ns    233315301
double dispatch                               2.47 ns         2.47 ns    280235599
multi-method call                             3.01 ns         3.01 ns    227033837
virtual function call                         2.00 ns         2.00 ns    342081472
uni-method call (virtual inheritance)         2.06 ns         2.06 ns    332755492
double dispatch(virtual inheritance)          2.25 ns         2.25 ns    300920778
multi-method call (virtual inheritance)       3.15 ns         3.15 ns    225812327
virtual function call (hash info in gv)       1.81 ns         1.81 ns    373747104
**Segmentation fault (core dumped)**

Missing include

If you add target_compile_definitions(yomm2 PUBLIC YOMM2_ENABLE_TRACE=0) to top level CMakeLists.txt, and build the project's example, it will results

D:\yomm2\src\yomm2.cpp(858,42): error C2079: 'discard_log' uses undefined class 'std::basic_ostringstream<char,std::char_traits<char>,std::allocator<char>>' [D:\yomm2\build\src\yomm2.vcxproj]
D:\yomm2\src\yomm2.cpp(864,50): error C2446: ':': no conversion from 'std::ostringstream' to 'std::ostream' [D:\yomm2\build\src\yomm2.vcxproj]

Compiler:

Microsoft (R) C/C++ Optimizing Compiler Version 19.26.28806 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

error in runtime destructor

HI @jll63,

Thanks for the lib again, still playing with it. Seeing this error in runtime destructor, do you know what may cause this? (not repro debug build on mac, haven't done debugging it on linux server yet assuming this is something wrong related to runtime struct for certain case)

*** Error in `./NodeServer': free(): corrupted unsorted chunks: 0x000000000155b170 ***
*** Aborted at 1563204608 (unix time) try "date -d @1563204608" if you are using GNU date ***
PC: @ 0x0 (unknown)
*** SIGABRT (@0x3bb20000227b) received by PID 8827 (TID 0x7f8a872cf080) from PID 8827; stack trace: ***
@ 0x7f8a86cb6330 (unknown)
@ 0x7f8a860dfc37 gsignal
@ 0x7f8a860e3028 abort
@ 0x7f8a8611c2a4 (unknown)
@ 0x7f8a8612882e (unknown)
@ 0x698710 __gnu_cxx::new_allocator<>::deallocate()
@ 0x695988 std::allocator_traits<>::deallocate()
@ 0x690eee std::_Vector_base<>::_M_deallocate()
@ 0x68b38f std::_Vector_base<>::~_Vector_base()
@ 0x687785 std::vector<>::~vector()
@ 0x687120 yorel::yomm2::detail::runtime::~runtime()
@ 0x681898 yorel::yomm2::detail::update_methods()
@ 0x6862f5 yorel::yomm2::update_methods()
@ 0x41ebdd RunServer()
@ 0x41abda main

call stack in GDB:
(gdb) bt
#0 0x00007ffff6df1c37 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1 0x00007ffff6df5028 in __GI_abort () at abort.c:89
#2 0x00007ffff6e2e2a4 in __libc_message (do_abort=do_abort@entry=1, fmt=fmt@entry=0x7ffff6f40350 "*** Error in `%s': %s: 0x%s \n")
at ../sysdeps/posix/libc_fatal.c:175
#3 0x00007ffff6e3a82e in malloc_printerr (ptr=, str=0x7ffff6f404a0 "free(): corrupted unsorted chunks", action=1) at malloc.c:4998
#4 _int_free (av=, p=, have_lock=0) at malloc.c:3842
#5 0x00000000006b269a in __gnu_cxx::new_allocatoryorel::yomm2::detail::rt_method::deallocate(yorel::yomm2::detail::rt_method
, unsigned long) ()
#6 0x00000000006b011c in std::allocator_traits<std::allocatoryorel::yomm2::detail::rt_method >::deallocate(std::allocatoryorel::yomm2::detail::rt_method&, yorel::yomm2::detail::rt_method
, unsigned long) ()
#7 0x00000000006abcec in std::_Vector_base<yorel::yomm2::detail::rt_method, std::allocatoryorel::yomm2::detail::rt_method >::_M_deallocate(yorel::yomm2::detail::rt_method
, unsigned long) ()
#8 0x00000000006a6703 in std::_Vector_base<yorel::yomm2::detail::rt_method, std::allocatoryorel::yomm2::detail::rt_method >::~_Vector_base() ()
#9 0x00000000006a2f8b in std::vector<yorel::yomm2::detail::rt_method, std::allocatoryorel::yomm2::detail::rt_method >::~vector() ()
#10 0x00000000006a29c6 in yorel::yomm2::detail::runtime::~runtime() ()
#11 0x000000000069d306 in yorel::yomm2::detail::update_methods(yorel::yomm2::detail::registry const&, yorel::yomm2::detail::dispatch_data&) ()
#12 0x00000000006a1d63 in yorel::yomm2::update_methods() ()
#13 0x0000000000413622 in main (argc=1, argv=0x7fffffffe808) at /home/shawncao/nebula/src/service/node/NodeServer.cpp:178

Dispatch on arbitrary values

Hello and thanks for the wonderful library.

Is there any way to dispatch on arbitary values, not just types? Coming from a clojure background, I am trying to replicate some of the behaviour of clojures multimethods.

In clojure the method declaration takes a function some_fn and before dispatching, it calls some_fn(arg1,args2, ...) and then dispatches based on the return value of that function.

This enables for example dispatching on certain values in the argument and adding new behaviour later based on these fields.

Some pseudo code of what I wanted to write:

struct Customer{
  //some fields
  int accessLevel;
};

declare_method(std::string, purchase_rare_item, [](Customer& c){return c.accessLevel;});

define_method(std::string, purchase_rare_item, default) {
    return "not allowed";
}

define_method(std::string, purchase_rare_item, 4) {
    return "please refer to the VIP auction page"
}

define_method(std::string, purchase_rare_item, 5) {
    return "thank you for your purchase."
}

The alternative would be to write a closed switch.

My current workaround idea is just to create a lot of classes, so I can have the type dispatch. But it gets quite ugly. I would like for example to dispatch on whatever is the first item in a std::vector and I feel bad creating subtypes of that. Or is your advice to just go with this?

What if the existing class I want to dispatch on has no virtual functions? Should I just wrap them in one with for example a virtual destructor?

Question: am I able to pass non-virtual parameter to method definition?

Thanks for the library - it looks great!

I was trying the method declare and define, it seems failing compiling, so ask if this is supported?

declare_method(std::unique_ptr, asBuffer,
(yorel::yomm2::virtual_<RowCursor&>, std::shared_ptr <Schema&>&);

define_method(std::unique_ptr, asBuffer,
(RowCursor& c, std::shared_ptr<Schema&>& s){
}

define_method(std::unique_ptr, asBuffer,
(CompositeRowCursor& d, std::shared_ptr<Schema&>& s){
}

Issues with Debug build of depending library (due to recent changes)

Problem

Our library, here called example, uses YOMM2.
Building example in Release works fine, but when building example in Debug configuration we get the following error during linking:

/usr/bin/ld: ../../lib/libexample.so: undefined reference to `yorel::yomm2::detail::log()'

Explanation

This problem is introduced in PR #16 since it will now only define yomm2::detail::log() when YOMM2_ENABLE_TRACE is true.
YOMM2_ENABLE_TRACE is set to true depending on the NDEBUG flag in the yomm2.hpp header, meaning that it actually depends on the including libraries compilation settings (which is fine I guess).
The issue is using this flag in the compiled yomm2.cpp in a way that breaks this dependency.
#16 causes the problem by encapsulating the entire definition of log() inside an if statement depending on the value of YOMM2_ENABLE_TRACE at the time of compiling YOMM2.
However, log() is still used depending on the value of YOMM2_ENABLE_TRACE at the time of compiling the depending library.

Suggestion

The goal is to meet the requirement in #15 (which is fixed by #16) of being able to compile with
target_compile_definitions(yomm2 PUBLIC YOMM2_ENABLE_TRACE=0)
in the top-level CMakeLists.txt, thus the added #include <sstream> needs to stay.
However, the check on YOMM2_ENABLE_TRACE around the log() function are not necessary, and the definition of a simple function is no significant burden on the (either way short) compilation.
Thus, my suggestion is to remove these checks.

CMake warning related to policy CMP0048

First of all thank you for developing this great library, we are using it massively in our code.

Problem

One minor issue we have is that when building it automatically as an external dependency from the cmake script of our library, the following warning is generated

Policy CMP0048 is not set: project() command manages VERSION variables.
Run "cmake --help-policy CMP0048" for policy details. Use the cmake_policy
command to set the policy and suppress this warning.

The following variable(s) would be set to empty:

PROJECT_VERSION
PROJECT_VERSION_MAJOR
PROJECT_VERSION_MINOR

This is probably due to newer versions expecting the project version to be set in the project command.
We try to minimize the amount of warnings and this is one of the last ones, so the perfectionist in me was motivated enough to write this issue.
I hope you don't mind me taking your time on such a minor problem.

project (YOMM2)

set (YOMM2_VERSION_MAJOR 1)
set (YOMM2_VERSION_MINOR 0)

Suggestion

One option would be to require cmake 3.0 or higher which allows specifying the version in the project command and automatically defines YOMM2_VERSION_MINOR and YOMM2_VERSION_MAJOR.
See here for more detail (3.0 version of documentation on purpose).
This would remove warnings generated when building the library.

The resulting start of CMakeLists.txt would then become

# <COPYRIGHT NOTICE>

cmake_minimum_required (VERSION 3.0)

project (YOMM2 VERSION 1.0)

find_package(Boost)

# <Rest of script as is>

I will make an according small pull request and link this issue to it.

Comma inside declare_method

When I try to use a tuple (or anything including a comma in it's definition) inside declare_method, the macro seems unable to parse the arguments.

Minimal example:

#include "yorel/yomm2/cute.hpp"
using yorel::yomm2::virtual_;

#include <tuple>

class Matrix;

declare_method(void, test, (virtual_<const Matrix&>, std::tuple<int, int>))

The errors are something like error: template argument 1 is invalid and error: ‘yorel::yomm2::detail::virtual_arg_t’ is not a type.

I still do not understand the library well enough to understand why exactly this is the case or if it is fixable, but I would love to get some feedback on this!

Friendship with multi-methods

I have been a user of yomm11 for quite some time. At the time where I started using it I faced a problem with the use of multi-methods from classes, which I solved by using a suggestion by @jll63.

The example elaborated at the time is attached for reference: much like in the matrix example, multi-methods are used to handle a hierarchy of classes, however in this case the "entrypoint" is a public method of the Painter class.

The requirement was to access some private member of the Painter class from the multi-methods. Since it is not possible to declare a multi-method as a member of the class, the solution was to friend the class generated by the preprocessor, and pass the instance to the various multi-methods.

However, in yomm2 the same solution seems more difficult to achieve: the library generates a numeric ID for the namespace where it declares the methods, which I presume depends on the order of declaration and thus cannot be reliably predicted. Attached is my attempt to solve this, but I have the feeling that it is a brittle solution.

Any suggestions for the above? Would a variant of declare_method and define_method, to use within a class eligible for implementation? For example:

class Painter
{
public:
    void paint(const Geometry& geometry);
private:
    int counter = 0;
    declare_class_method(void, paintObject, (virtual_<const Geometry&>));
};

define_class_method(void, Painter, paintObject, (const Geometry& /*geometry*/)) {
    // ...
}

Error when compiling with raw non-const ptr

with virtual_<matrix*>
the following code

Click to expand!
#include <iostream>
#include <string>

#include <yorel/yomm2/cute.hpp>

using std::string;
using yorel::yomm2::virtual_;

struct matrix {
    virtual ~matrix() {}
};
struct dense_matrix : matrix {};
struct diagonal_matrix : matrix {};

register_class(matrix);
register_class(dense_matrix, matrix);
register_class(diagonal_matrix, matrix);

declare_method(string, to_json, (virtual_<matrix*>));

define_method(string, to_json, (dense_matrix* m)) {
    return "json for dense matrix ptr...";
}

define_method(string, to_json, (diagonal_matrix* m)) {
    return "json for diagonal matrix ptr...";
}

int main() {
    using std::cout;
    using std::cerr;

    yorel::yomm2::update_methods();

    dense_matrix _a{};
    diagonal_matrix _b{};
    auto a = &_a;
    auto b = &_b;

    #pragma clang diagnostic ignored "-Wpotentially-evaluated-expression"

    cout << to_json(a) << "\n"; // json for dense matrix ptr
    cout << to_json(b) << "\n"; // json for diagonal matrix ptr

    return 0;
}

triggers compiling error

D:\yomm2\examples\matrix.cpp(40,13): warning C4068: unknown pragma [D:\yomm2\build\examples\matrix.vcxproj]
D:\yomm2\include\yorel/yomm2.hpp(961,7): error C2665: 'yorel::yomm2::detail::resolver<1,yorel::yomm2::virtual_<matrix *>>::resolve': none of the 2 overloads could convert all the argument types [D:\yomm2\build\examples\matrix.vcxproj]
D:\yomm2\include\yorel/yomm2.hpp(710,18): message : could be 'void *yorel::yomm2::detail::resolver<1,yorel::yomm2::virtual_<matrix *>>::resolve(const yorel::yomm2::detail::word *,uintptr_t,size_t,const yorel::yomm2::detail::word *,const T &)' [D:\yomm2\build\examples\matrix.vcxproj]
          with
          [
              T=matrix *
          ]
D:\yomm2\include\yorel/yomm2.hpp(692,18): message : or       'void *yorel::yomm2::detail::resolver<1,yorel::yomm2::virtual_<matrix *>>::resolve(const yorel::yomm2::detail::word *,uintptr_t,size_t,yorel::yomm2::detail::word,const T &)' [D:\yomm2\build\examples\matrix.vcxproj]
          with
          [
              T=matrix *
          ]
D:\yomm2\include\yorel/yomm2.hpp(959,1): message : while trying to match the argument list '(yorel::yomm2::detail::word *, uintptr_t, size_t, yorel::yomm2::detail::word, const T *)' [D:\yomm2\build\examples\matrix.vcxproj]
          with
          [
              T=matrix
          ]
D:\yomm2\include\yorel/yomm2.hpp(959): message : while compiling class template member function 'void *yorel::yomm2::detail::method<_yomm2_method_to_json,std::string (yorel::yomm2::virtual_<matrix *>),yorel::yomm2::default_policy>::resolve(yorel::yomm2::policy::hash_factors_in_globals,const T *)' [D:\yomm2\build\examples\matrix.vcxproj]
          with
          [
              T=matrix
          ]
D:\yomm2\include\yorel/yomm2.hpp(955): message : see reference to function template instantiation 'void *yorel::yomm2::detail::method<_yomm2_method_to_json,std::string (yorel::yomm2::virtual_<matrix *>),yorel::yomm2::default_policy>::resolve(yorel::yomm2::policy::hash_factors_in_globals,const T *)' being compiled [D:\yomm2\build\examples\matrix.vcxproj]
          with
          [
              T=matrix
          ]
D:\yomm2\examples\matrix.cpp(19): message : see reference to class template instantiation 'yorel::yomm2::detail::method<_yomm2_method_to_json,std::string (yorel::yomm2::virtual_<matrix *>),yorel::yomm2::default_policy>' being compiled [D:\yomm2\build\examples\matrix.vcxproj]

With const matrix* const dense_matrix* and const diagonal_matrix* all work fine.

Dispatch table generation?

I enjoyed your articles on the algorithms and data structures you used to generate yomm11's dispatch tables. I don't see any comparable description for yomm2, and the comparison to yomm11 suggests the internals are substantially different. If this is the case, do you expect to write an article or summary of yomm2's algorithms?

Boost always installs even though package exists on system

find_package(
${PACKAGE} QUIET
HINTS ${DEPENDENCY_INSTALL_PREFIX} ${CMAKE_INSTALL_PREFIX}
)
if(NOT ${${PACKAGE}_FOUND})

The call to find_package in the script forces to the CONFIG mode due to HINTS manually inserted. It means it skips the FindBoost module, thus does not find any pre-installed Boost leading to always downloading the package.

Hence the vcpkg patch there https://github.com/microsoft/vcpkg/blob/b4a3d89125e45bc8f80fb94bef9761d4f4e14fb9/ports/yomm2/fix_install.patch#L9-L10
which could also be replaced by

diff --git a/cmake/find_or_download_package.cmake b/cmake/find_or_download_package.cmake
index 7e8a282..f550c2d 100644
--- a/cmake/find_or_download_package.cmake
+++ b/cmake/find_or_download_package.cmake
@@ -7,6 +7,5 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
   set(DEPENDENCY_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/dependencies/${PACKAGE})
   find_package(
     ${PACKAGE} QUIET
-     HINTS ${DEPENDENCY_INSTALL_PREFIX} ${CMAKE_INSTALL_PREFIX}
   )
  if(NOT ${${PACKAGE}_FOUND})

The best would be to clear out the CMake scripts related to Boost and rely on find_package + FetchContent https://github.com/boostorg/cmake

Possibly missing include in header?

It seems like include/yorel/yomm2.hpp is missing an include for std::uintptr_t!
At least on my system the compilation now fails.

std::uintptr_t is defined in <cstdint>, and adding that include fixes the issue for me.

update_methods segfault if I forget a register_class

I enjoyed your cppcon video, and I'm having fun playing with your library - thanks!

If I forget a define_method, I can get an error callback with set_method_call_error_handler.

If I forget a register_class, I get a segfault during update_methods.

I understand that it can't just magically work, and I like that it fails early ( during update_methods, which hopefully I'm calling right from the top of main ).

Is there any way to throw, or at least abort with relavent stderr text in this case ( without significant additional expense )?

I can provide a sample if necessary, but just removing a register_class from an example should do it.

Rvalue parameters

Declaring methods with rvalue parameters doesn't seem to work.
E.g. something like,

declare_method(float, my_function, (virtual_<matrix&&>));

produces a rather long error message (using g++ 7.2) with repeated error messages saying:
error: ‘<anonymous>’ has incomplete type

Duplicated definitions lead to dangerous error

When you accidentally define method for the same type twice, it compiles and works, but explodes violently when you call the method.

Example:

#include <iostream>
#include <memory>
#include <string>

#include <yorel/yomm2/keywords.hpp>

struct Character {
    virtual ~Character() {
    }
};

struct Warrior : Character { std::string name; };
struct Dog : Character { };

register_classes(Character, Warrior, Dog);

declare_method(std::string, say, (virtual_<const Character&>));

define_method(std::string, say, (const Character& x)) {
    return "hey!";
}

define_method(std::string, say, (const Warrior& x)) {
    return std::string("I'm ") + x.name + "!";
}

define_method(std::string, say, (const Warrior& x)) {
    return "bark!";
}

int main() {
    yorel::yomm2::update_methods();

    {
        auto x = std::make_unique<Warrior>();
        x->name = "john";

        std::cout << say(*x) << std::endl;
    }

    return 0;
}

Output:

a for N5yorel5yomm26methodI11YoMm2_S_sayFNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEENS0_8virtual_IRK9CharacterEEENS0_6policy22hash_factors_in_methodEEE(Segmentation fault (core dumped)

Would be good to come up with some compile-time protection against this

specializing on `shared_ptr<Animal> const&` doesn't work for me.

Thanks for this library. It feels like I have a new superpower!

According to the reference doc, it should be possible to dispatch on shared_ptr<Animal> const& but if I add the const ref qualifier, I get lots of template error messages, the first of which says Static_assert failed due to requirement 'shared_ptr_traits<const std::__1::shared_ptr<ScalarT<double> > &>::is_shared_ptr'. I assume I am doing something wrong.

Here is an example. It works as is. But if I change the #if 1 to #if 0 to enable const ref for all arguments, then it doesn't compile.

I am compiling with Clang in Xcode 12.3.

#include <yorel/yomm2/cute.hpp>
#include <iostream>
#include <memory>
#include <string>

#if 1
#define CONSTREF
#else
#define CONSTREF const&
#endif

using yorel::yomm2::virtual_;

class Value { // base class for values
public:
	virtual ~Value() = default;
};

class Scalar : public Value {};  // base class for scalars

template <class T>
class ScalarT : public Scalar {
	T x_;
public:
	ScalarT(T x) : x_(x) {}
	T x() const noexcept { return x_; }
};

// convenience function to create a shared ScalarT.
template <class T>
std::shared_ptr<Value> num(T x) { return std::make_shared<ScalarT<T>>(x); }

register_class(Value);
register_class(Scalar, Value);
register_class(ScalarT<int>, Scalar);
register_class(ScalarT<double>, Scalar);

declare_method(std::shared_ptr<Value>, plus,
	(virtual_<std::shared_ptr<Value> CONSTREF>, virtual_<std::shared_ptr<Value> CONSTREF>));
	
declare_method(std::string, str,
	(virtual_<std::shared_ptr<Value> CONSTREF>));

define_method(std::shared_ptr<Value>, plus,
	(std::shared_ptr<Value> CONSTREF a, std::shared_ptr<Value> CONSTREF b))
{
	throw std::runtime_error("unsupported values in plus");
}

define_method(std::shared_ptr<Value>, plus,
	(std::shared_ptr<ScalarT<int>> CONSTREF a, std::shared_ptr<ScalarT<int>> CONSTREF b))
{
	return num(a->x() + b->x());
}

define_method(std::shared_ptr<Value>, plus,
	(std::shared_ptr<ScalarT<double>> CONSTREF a, std::shared_ptr<ScalarT<double>> CONSTREF b))
{
	return num(a->x() + b->x());
}

define_method(std::string, str, (std::shared_ptr<Value> CONSTREF a))
{
	throw std::runtime_error("unsupported value in str");
}

define_method(std::string, str, (std::shared_ptr<ScalarT<int>> CONSTREF a))
{
	return std::to_string(a->x());
}

define_method(std::string, str, (std::shared_ptr<ScalarT<double>> CONSTREF a))
{
	return std::to_string(a->x());
}

int main(int argc, const char * argv[])
{
	yorel::yomm2::update_methods();
	
	auto a = num(1);
	auto b = num(2);
	auto c = num(3.01);
	auto d = num(4.001);
	std::cout << str(plus(a, b)) << "\n";
	std::cout << str(plus(c, d)) << "\n";
	return 0;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.