init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

37
cmake/external/glib/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,37 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_glib INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_glib ALIAS external_glib)
find_package(PkgConfig REQUIRED)
pkg_check_modules(DESKTOP_APP_GLIB2 REQUIRED IMPORTED_TARGET glib-2.0 gobject-2.0 gio-2.0 gio-unix-2.0)
target_link_libraries(external_glib
INTERFACE
PkgConfig::DESKTOP_APP_GLIB2
)
target_compile_definitions(external_glib
INTERFACE
GLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_56
GLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_56
)
block()
set(BUILD_TESTING OFF)
set(BUILD_DOC OFF)
set(BUILD_EXAMPLES OFF)
add_subdirectory(cppgir EXCLUDE_FROM_ALL)
endblock()
include(generate_cppgir.cmake)
if (DESKTOP_APP_GLIB2_gio-unix-2.0_VERSION VERSION_GREATER_EQUAL 2.86)
generate_cppgir(external_glib GioUnix-2.0)
else()
generate_cppgir(external_glib Gio-2.0)
endif()

View File

@@ -0,0 +1,30 @@
Language: Cpp
Standard: Cpp11
BasedOnStyle: llvm
AlignAfterOpenBracket: DontAlign
PenaltyBreakBeforeFirstCallParameter: 200
IndentWidth: 2
AllowShortFunctionsOnASingleLine: All
KeepEmptyLinesAtTheStartOfBlocks: false
#BreakBeforeBraces: Linux
BreakBeforeBraces: Custom
BraceWrapping:
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterStruct: true
AfterClass: true
AfterUnion: true
SplitEmptyFunction: false
SplitEmptyRecord: false
ColumnLimit: 80
ContinuationIndentWidth: 4
PointerAlignment: Right
AlwaysBreakAfterReturnType: TopLevelDefinitions
#BreakInheritanceList: AfterColon
FixNamespaceComments: true
IndentCaseLabels: true
AlwaysBreakTemplateDeclarations: true
SpaceAfterTemplateKeyword: false
#SpaceBeforeCpp11BracedList: true
AlignEscapedNewlines: DontAlign

View File

@@ -0,0 +1,54 @@
variables:
GIT_SUBMODULE_STRATEGY: recursive
stages:
- build
default:
image: registry.gitlab.com/mnauw/cppgir:noble
ubuntu-focal-gcc:
image: registry.gitlab.com/mnauw/cppgir:focal
stage: build
script:
- mkdir build-gcc
- cd build-gcc
- cmake ..
- make
- make examples
except:
- tags
ubuntu-focal-clang:
image: registry.gitlab.com/mnauw/cppgir:focal
stage: build
script:
- mkdir build-clang
- cd build-clang
- CC=clang-11 CXX=clang++-11 cmake ..
- make
- make examples
except:
- tags
ubuntu-noble-gcc-13:
stage: build
script:
- mkdir build-gcc-13
- cd build-gcc-13
- cmake ..
- make
- make examples
except:
- tags
ubuntu-noble-clang-18:
stage: build
script:
- mkdir build-clang-18
- cd build-clang-18
- CC=clang-18 CXX=clang++-18 cmake ..
- make
- make examples
except:
- tags

View File

@@ -0,0 +1,15 @@
FROM ubuntu:noble
RUN apt-get update
RUN apt-get -y --no-install-recommends install \
build-essential cmake libboost-program-options-dev libboost-fiber-dev \
libfmt-dev ronn clang-18 clang-tools-18 \
libgirepository1.0-dev libgstreamer1.0-dev \
libgtk-3-dev gir1.2-gtk-3.0 \
libgtk-4-dev gir1.2-gtk-4.0 \
qtbase5-dev meson git
RUN apt-get clean
WORKDIR /build
ENV LANG C.UTF-8

View File

@@ -0,0 +1,9 @@
set -e
TAG="registry.gitlab.com/mnauw/cppgir:jammy"
docker build --build-arg HOST_USER_ID="$UID" --tag "${TAG}" \
--file "Dockerfile" .
docker run --rm \
--volume "$(pwd)/..:/home/user/app" --workdir "/home/user/app" \
--tty --interactive "${TAG}" bash

View File

@@ -0,0 +1,3 @@
[submodule "expected-lite"]
path = expected-lite
url = https://github.com/martinmoene/expected-lite.git

View File

@@ -0,0 +1,582 @@
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
if (POLICY CMP0077)
cmake_policy(SET CMP0077 NEW)
endif ()
if (POLICY CMP0069)
cmake_policy(SET CMP0069 NEW)
endif ()
if (POLICY CMP0167)
# try to use FindBoost for now
cmake_policy(SET CMP0167 OLD)
endif ()
project(cppgir VERSION 2.0.0)
include(FindPkgConfig)
include(GNUInstallDirs)
include(CTest)
enable_testing()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 14)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
add_compile_options (-Wall -Wextra $<$<COMPILE_LANGUAGE:CXX>:-Wnon-virtual-dtor>)
endif()
# clang debug stdc++
if (${CMAKE_C_COMPILER_ID} STREQUAL "Clang")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fstandalone-debug")
endif()
## OPTIONS ##
option(BUILD_DOC "build documentation" ON)
option(BUILD_TOOLS "build generator tool" ON)
option(BUILD_EXAMPLES "build examples" ON)
option(BUILD_EXAMPLES_MODULES "build module examples" OFF)
option(BUILD_EXAMPLES_FORCE "build failing module examples" OFF)
set(BUILD_EXAMPLES_OPTIONS "" CACHE STRING "example compile options")
option(BUILD_EMBED_IGNORE "embed default ignore" OFF)
option(INTERNAL_EXPECTED "use internal expected-lite" ON)
option(LINK_STDFS "link to stdc++fs" OFF)
set(BUILD_FMT AUTO CACHE STRING "format library")
set_property(CACHE BUILD_FMT PROPERTY STRINGS AUTO FMTLIB STDFORMAT)
set(GTK_MAJOR GTK3 CACHE STRING "gtk version")
set_property(CACHE GTK_MAJOR PROPERTY STRINGS GTK3 GTK4)
set(GIR_DIR ${CMAKE_INSTALL_FULL_DATADIR}/gir-1.0 CACHE STRING "extra GIR search directory")
# the following will have gir-1.0 appended (for legacy reasons)
# best left as-is to follow standard GIR search
set(GIR_DEFAULT_DIRS "/usr/local/${CMAKE_INSTALL_DATADIR}:/usr/${CMAKE_INSTALL_DATADIR}"
CACHE STRING "fallback GIR search prefix paths (to be suffixed with gir-1.0)")
## CONTENT ##
if (BUILD_TOOLS)
find_package(Boost 1.58 REQUIRED)
# check fmtlib
pkg_check_modules(FORMAT fmt)
if (FORMAT_FOUND)
set(FORMAT_LIBRARIES "${FORMAT_LDFLAGS}")
else ()
# fallback for old version without pkg-config
find_path(FORMAT_INCLUDE_DIRS fmt/format.h)
find_library(FORMAT_LIBRARIES fmt)
endif ()
if ("${FORMAT_INCLUDE_DIRS}" STREQUAL "FORMAT_INCLUDE_DIRS-NOTFOUND" OR
"${FORMAT_LIBRARIES}" STREQUAL "FORMAT_LIBRARIES-NOTFOUND")
set(HAVE_FMTLIB OFF)
else()
set(HAVE_FMTLIB ON)
endif()
message(STATUS "fmtlib available status: ${HAVE_FMTLIB}")
# check C++20 format
try_compile(HAVE_STDFORMAT ${CMAKE_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_LIST_DIR}/cmake/cpp20_format.cpp
CXX_STANDARD 20)
message(STATUS "std::format available status: ${HAVE_STDFORMAT}")
# check and decide on which fmt to use
set(USE_FMTLIB NONE)
if (BUILD_FMT STREQUAL "AUTO")
if (HAVE_FMTLIB)
set(USE_FMTLIB ON)
elseif (HAVE_STDFORMAT)
set(USE_FMTLIB OFF)
endif()
elseif(BUILD_FMT STREQUAL "FMTLIB" AND HAVE_FMTLIB)
set(USE_FMTLIB ON)
elseif(BUILD_FMT STREQUAL "STDFORMAT" AND HAVE_STDFORMAT)
set(USE_FMTLIB OFF)
endif()
if (USE_FMTLIB STREQUAL "NONE")
message (FATAL_ERROR "no format library found")
endif()
# check C++20 coroutine
try_compile(HAVE_CORO ${CMAKE_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_LIST_DIR}/cmake/cpp20_coro.cpp
CXX_STANDARD 20)
message(STATUS "coroutine support available status: ${HAVE_CORO}")
# required ignore file
set(GI_IGNORE_FILE_DIR data)
set(GI_IGNORE_FILE cppgir.ignore)
file(READ data/cppgir.ignore CPPGIR_IGNORE)
if (UNIX)
set(GI_IGNORE_FILE_PLATFORM cppgir_unix.ignore)
file(READ data/cppgir_unix.ignore CPPGIR_UNIX_IGNORE)
set(CPPGIR_WIN_IGNORE "")
else ()
set(GI_IGNORE_FILE_PLATFORM cppgir_win.ignore)
file(READ data/cppgir_win.ignore CPPGIR_WIN_IGNORE)
set(CPPGIR_UNIX_IGNORE "")
endif ()
set(GI_IGNORE_FILE_INSTALL_DIR ${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME})
add_executable(cppgir tools/cppgir.cpp
tools/genbase.cpp tools/genbase.hpp
tools/genns.cpp tools/genns.hpp
tools/genutils.cpp tools/genutils.hpp
tools/function.cpp tools/function.hpp
tools/repository.cpp tools/repository.hpp
tools/common.hpp)
target_link_libraries(cppgir Boost::boost)
if (UNIX)
# add fixed fallback search places
target_compile_definitions(cppgir PRIVATE
-DGI_GIR_DIR=${GIR_DIR}
-DGI_DATA_DIR=${CMAKE_INSTALL_FULL_DATADIR}/gir-1.0
)
if (GIR_DEFAULT_DIRS)
target_compile_definitions(cppgir PRIVATE
-DDEFAULT_GIRPATH=${GIR_DEFAULT_DIRS}
)
endif ()
endif ()
if (BUILD_EMBED_IGNORE)
# generate embedded ignore data
configure_file(tools/ignore.hpp.in tools/ignore.hpp @ONLY)
target_include_directories(cppgir PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/tools)
set(GENERATED_IGNORE "")
else()
target_compile_definitions(cppgir PRIVATE
-DDEFAULT_IGNORE_FILE=${GI_IGNORE_FILE_INSTALL_DIR}/${GI_IGNORE_FILE}:${GI_IGNORE_FILE_INSTALL_DIR}/${GI_IGNORE_FILE_PLATFORM})
set(GENERATED_IGNORE --ignore ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE}:${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE_PLATFORM})
endif()
if (USE_FMTLIB)
target_link_libraries(cppgir ${FORMAT_LIBRARIES})
set_property(TARGET cppgir PROPERTY CXX_STANDARD 17)
else()
set_property(TARGET cppgir PROPERTY CXX_STANDARD 20)
endif()
if (LINK_STDFS)
# some older gcc might sometimes (?) need this, even in c++17 mode
# see issue #80
target_link_libraries(cppgir stdc++fs)
endif ()
add_executable(CppGir::cppgir ALIAS cppgir)
endif () # BUILD_TOOLS
add_library(gi INTERFACE)
target_include_directories(gi INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/override>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/override>"
)
add_library(CppGir::gi ALIAS gi)
if (INTERNAL_EXPECTED)
set(EXPECTED_LITE_INCLUDE "expected-lite/include")
if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/${EXPECTED_LITE_INCLUDE}/nonstd/expected.hpp)
target_include_directories(gi INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${EXPECTED_LITE_INCLUDE}>"
)
else ()
message (FATAL_ERROR "missing submodule expected-lite")
endif ()
else ()
find_package(expected-lite)
if (expected-lite_FOUND)
target_link_libraries(gi INTERFACE nonstd::expected-lite)
else ()
target_compile_features(gi INTERFACE cxx_std_23)
message (WARNING "thus cppgir headers will require C++23")
endif ()
endif ()
if (BUILD_TOOLS)
if (BUILD_EXAMPLES OR BUILD_TESTING)
pkg_check_modules(GOBJECT gobject-2.0)
endif ()
if (BUILD_EXAMPLES)
pkg_check_modules(GIO gio-2.0 gio-unix-2.0)
pkg_check_modules(GST gstreamer-1.0)
if (GTK_MAJOR STREQUAL "GTK3")
pkg_check_modules(GTK gtk+-3.0)
set(GTK_GIR Gtk-3.0)
endif ()
if (GTK_MAJOR STREQUAL "GTK4")
# only plain gio is a dependency, but GIR requires other gio parts
pkg_check_modules(GTK gtk4 gtk4-unix-print gio-unix-2.0)
set(GTK_GIR Gtk-4.0)
endif ()
endif ()
## TEST ##
if (BUILD_TESTING AND GOBJECT_FOUND)
add_library(ltest
test/test_object.c test/test_object.h test/test_boxed.c test/test_boxed.h)
target_include_directories(ltest PUBLIC "gi" "override")
target_link_libraries(ltest gi ${GOBJECT_LDFLAGS})
target_compile_options(ltest PUBLIC ${GOBJECT_CFLAGS})
add_executable(gi-test test/main.cpp)
target_link_libraries(gi-test PRIVATE ltest)
add_test(NAME gi-test COMMAND gi-test)
# a C++17 version of the above
add_executable(gi-test-17 test/main.cpp)
target_link_libraries(gi-test-17 PRIVATE ltest)
set_property(TARGET gi-test-17 PROPERTY CXX_STANDARD 17)
add_test(NAME gi-test-17 COMMAND gi-test-17)
endif ()
## EXAMPLES ##
# generated wrappers' dir
set (GENERATED_DIR_DEFAULT "/tmp/gi")
set (EXAMPLES_LIBS "")
if (NOT GENERATED_DIR)
set (GENERATED_DIR ${GENERATED_DIR_DEFAULT})
endif ()
# arguments to generator
if (NOT DEFINED GENERATED_ARGS_DEFAULT)
set(GENERATED_ARGS_DEFAULT --class --class-full --call-args 0 --basic-collection)
endif ()
if (NOT DEFINED GENERATED_ARGS_EXTRA)
set(GENERATED_ARGS_EXTRA "")
endif ()
set (GENERATED_ARGS ${GENERATED_ARGS_DEFAULT} ${GENERATED_ARGS_EXTRA})
set(EXAMPLE_TARGETS "")
set(EXAMPLE_NS "")
# if both --dl and --expected are enabled,
# all function return value become gi::result<>
# to keep examples straight and simple,
# only few of them are adjusted to handle that scenario
set(PLAIN_API ON)
if ("${GENERATED_ARGS}" MATCHES "--dl")
set (EXAMPLES_LIBS ${CMAKE_DL_LIBS})
if ("${GENERATED_ARGS}" MATCHES "--expected")
set(PLAIN_API OFF)
endif ()
endif ()
if (BUILD_EXAMPLES)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-mxgot" NEEDS_LARGE_GOT)
endif ()
if (GOBJECT_FOUND)
add_executable(example-gobject EXCLUDE_FROM_ALL examples/gobject.cpp)
target_compile_options(example-gobject PRIVATE ${GOBJECT_CFLAGS})
target_link_libraries(example-gobject PRIVATE ${GOBJECT_LDFLAGS})
set_property(TARGET example-gobject PROPERTY CXX_STANDARD 14)
message(STATUS "adding GObject example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gobject)
set(EXAMPLE_NS ${EXAMPLE_NS} GObject-2.0)
endif ()
if (GIO_FOUND)
add_executable(example-gio EXCLUDE_FROM_ALL examples/gio.cpp)
target_compile_options(example-gio PRIVATE ${GIO_CFLAGS})
target_link_libraries(example-gio PRIVATE ${GIO_LDFLAGS})
set_property(TARGET example-gio PROPERTY CXX_STANDARD 17)
message(STATUS "adding Gio communication example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio)
endif ()
if (GIO_FOUND)
add_executable(example-gio-dbus-client EXCLUDE_FROM_ALL examples/gio-dbus-client.cpp)
target_compile_options(example-gio-dbus-client PRIVATE ${GIO_CFLAGS})
target_link_libraries(example-gio-dbus-client PRIVATE ${GIO_LDFLAGS})
message(STATUS "adding Gio dbus example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio-dbus-client)
set(EXAMPLE_NS ${EXAMPLE_NS} Gio-2.0)
endif ()
if (GIO_FOUND AND PLAIN_API)
find_package(Boost 1.65 COMPONENTS fiber)
if (Boost_FOUND)
# no import target; multiple calls do not override first call targets
add_executable(example-gio-async EXCLUDE_FROM_ALL examples/gio-async.cpp)
target_include_directories(example-gio-async PRIVATE ${Boost_INCLUDE_DIRS})
target_compile_options(example-gio-async PRIVATE ${GIO_CFLAGS})
target_link_libraries(example-gio-async PRIVATE ${GIO_LDFLAGS} ${Boost_LIBRARIES})
message(STATUS "adding Gio async example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio-async)
else ()
set(GIO_ASYNC_EXAMPLE_TARGET "")
message(STATUS "disabling Gio async example")
endif ()
endif ()
if (GIO_FOUND AND HAVE_CORO AND PLAIN_API)
add_executable(example-gio-async-co EXCLUDE_FROM_ALL examples/gio-async-co.cpp)
target_compile_options(example-gio-async-co PRIVATE ${GIO_CFLAGS})
target_link_libraries(example-gio-async-co PRIVATE ${GIO_LDFLAGS})
set_property(TARGET example-gio-async-co PROPERTY CXX_STANDARD 20)
message(STATUS "adding Gio async coroutine example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio-async-co)
set(EXAMPLE_NS ${EXAMPLE_NS} Gio-2.0)
endif ()
if (GST_FOUND AND PLAIN_API)
add_executable(example-gst EXCLUDE_FROM_ALL examples/gst.cpp)
# add generated files
foreach (GENSRC IN ITEMS ${GENERATED_DIR}/glib/glib.cpp
${GENERATED_DIR}/gst/gst.cpp ${GENERATED_DIR}/gobject/gobject.cpp)
target_sources(example-gst PRIVATE ${GENSRC})
set_property(SOURCE ${GENSRC} PROPERTY GENERATED true)
endforeach ()
target_link_libraries(example-gst PRIVATE ${GST_LDFLAGS})
target_compile_options(example-gst PRIVATE ${GST_CFLAGS})
# a lot of class methods are wrapped these days, so there is some overlap
# however, no class implementation is used here, so arrange for suppression
target_compile_definitions(example-gst PRIVATE GI_CLASS_IMPL_PRAGMA=1)
set_property(TARGET example-gst PROPERTY CXX_STANDARD 14)
if (NEEDS_LARGE_GOT)
set_property(SOURCE ${GENERATED_DIR}/gst/gst.cpp APPEND PROPERTY COMPILE_OPTIONS "-mxgot")
endif ()
message(STATUS "adding Gst example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gst)
set(EXAMPLE_NS ${EXAMPLE_NS} Gst-1.0)
function(add_gst_module_example TARGET_NAME)
add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL examples/gst.cpp)
# add generated files
foreach (GENSRC IN ITEMS ${GENERATED_DIR}/gobject/gobject.cppm
${GENERATED_DIR}/gmodule/gmodule.cppm ${GENERATED_DIR}/gst/gst.cppm)
target_sources(${TARGET_NAME} PRIVATE FILE_SET CXX_MODULES
BASE_DIRS ${GENERATED_DIR} FILES ${GENSRC})
set_property(SOURCE ${GENSRC} PROPERTY GENERATED true)
endforeach ()
target_link_libraries(${TARGET_NAME} PRIVATE ${GST_LDFLAGS})
target_compile_options(${TARGET_NAME} PRIVATE ${GST_CFLAGS})
target_compile_definitions(${TARGET_NAME} PRIVATE USE_GI_MODULE=1 GI_CLASS_IMPL_PRAGMA=1)
set_property(TARGET ${TARGET_NAME} PROPERTY CXX_STANDARD 20)
set_property(TARGET ${TARGET_NAME} PROPERTY CXX_SCAN_FOR_MODULES ON)
if (NEEDS_LARGE_GOT)
set_property(SOURCE ${GENERATED_DIR}/gst/gst.cppm APPEND PROPERTY COMPILE_OPTIONS "-mxgot")
endif ()
message(STATUS "adding Gst module example ${TARGET_NAME}")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} ${TARGET_NAME} PARENT_SCOPE)
endfunction ()
# (separate) module variant, similar to case above
# GCC fails to link this properly :-( (missing symbols in object files)
if (CMAKE_VERSION GREATER_EQUAL 3.28 AND BUILD_EXAMPLES_MODULES AND
(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR BUILD_EXAMPLES_FORCE))
add_gst_module_example(example-gst-mod)
add_gst_module_example(example-gst-mod-extern)
target_compile_definitions(example-gst-mod-extern PRIVATE GI_MODULE_EXTERN=1)
endif ()
endif ()
if (GTK_FOUND AND PLAIN_API)
add_executable(example-gtk EXCLUDE_FROM_ALL examples/gtk.cpp examples/gtk-obj.cpp)
target_compile_options(example-gtk PRIVATE ${GTK_CFLAGS})
target_link_libraries(example-gtk PRIVATE ${GTK_LIBRARIES})
target_compile_options(example-gtk PRIVATE -DEXAMPLES_DIR=${CMAKE_CURRENT_LIST_DIR}/examples)
if (NEEDS_LARGE_GOT)
set_property(SOURCE examples/gtk-obj.cpp APPEND PROPERTY COMPILE_OPTIONS "-mxgot")
endif ()
# sanity check
if (NOT GTK_GIR)
message(FATAL_ERROR "unknown Gtk GIR")
endif ()
message(STATUS "adding Gtk example ${GTK_GIR}")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gtk)
set(EXAMPLE_NS ${EXAMPLE_NS} ${GTK_GIR})
function(add_gtk_module_example TARGET_NAME)
add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL)
target_sources(${TARGET_NAME} PRIVATE examples/gtk.cpp
PRIVATE FILE_SET CXX_MODULES BASE_DIRS ${GENERATED_DIR}
FILES ${GENERATED_DIR}/gtk/gtk_rec.cppm)
target_compile_options(${TARGET_NAME} PRIVATE ${GTK_CFLAGS})
target_link_libraries(${TARGET_NAME} PRIVATE ${GTK_LIBRARIES})
target_compile_options(${TARGET_NAME} PRIVATE -DUSE_GI_MODULE=1
-DEXAMPLES_DIR=${CMAKE_CURRENT_LIST_DIR}/examples)
set_property(TARGET ${TARGET_NAME} PROPERTY CXX_STANDARD 20)
set_property(TARGET ${TARGET_NAME} PROPERTY CXX_SCAN_FOR_MODULES ON)
set_property(SOURCE ${GENERATED_DIR}/gtk/gtk_rec.cppm PROPERTY GENERATED ON)
if (NEEDS_LARGE_GOT)
set_property(SOURCE ${GENERATED_DIR}/gtk/gtk_rec.cppm APPEND PROPERTY COMPILE_OPTIONS "-mxgot")
endif ()
message(STATUS "adding Gtk recursive module example ${TARGET_NAME}")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} ${TARGET_NAME} PARENT_SCOPE)
endfunction ()
# (recursive) module variant, similar to non-module case above
if (CMAKE_VERSION GREATER_EQUAL 3.28 AND BUILD_EXAMPLES_MODULES)
add_gtk_module_example(example-gtk-mod)
add_gtk_module_example(example-gtk-mod-extern)
target_compile_definitions(example-gtk-mod-extern PRIVATE GI_MODULE_EXTERN=1)
endif ()
endif ()
# optional Qt example
if (BUILD_EXAMPLES)
find_package(Qt5Core 5.9)
endif ()
if (Qt5Core_FOUND AND GIO_FOUND AND PLAIN_API)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_executable(example-gio-qt-async EXCLUDE_FROM_ALL examples/gio-qt-async.cpp)
target_compile_options(example-gio-qt-async PRIVATE ${GIO_CFLAGS})
target_link_libraries(example-gio-qt-async PRIVATE ${GIO_LDFLAGS} Qt5::Core)
set_target_properties(example-gio-qt-async PROPERTIES AUTOMOC ON)
message(STATUS "adding Qt GIO async example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio-qt-async)
endif ()
add_custom_command(OUTPUT ${GENERATED_DIR}
COMMENT "Generating wrapper code for: ${EXAMPLE_NS}"
DEPENDS cppgir ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE} ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE_PLATFORM}
COMMAND cppgir ${GENERATED_ARGS} ${GENERATED_IGNORE}
--output ${GENERATED_DIR} ${EXAMPLE_NS}
COMMAND cmake -E touch_nocreate ${GENERATED_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
# example might have been added if dependencies found
# make sure to disable as needed
if (NOT BUILD_EXAMPLES)
set(EXAMPLE_TARGETS "")
endif ()
message(STATUS "example programs: ${EXAMPLE_TARGETS}")
add_custom_target(examples)
if (EXAMPLE_TARGETS)
add_dependencies(examples ${EXAMPLE_TARGETS})
endif ()
add_custom_target(wrappers DEPENDS ${GENERATED_DIR})
foreach (example ${EXAMPLE_TARGETS})
target_link_libraries(${example} PRIVATE gi ${EXAMPLES_LIBS})
target_include_directories(${example} PRIVATE ${GENERATED_DIR})
target_compile_options(${example} PRIVATE ${BUILD_EXAMPLES_OPTIONS})
add_dependencies(${example} wrappers)
endforeach ()
## INSTALL ##
# manpage processor
find_program(RONN ronn DOC "ronn markdown man page processor")
if (${RONN} STREQUAL "RONN-NOTFOUND")
message(STATUS "ronn manpage processor not found; not building manpage")
elseif (BUILD_DOC)
message(STATUS "building manpage")
add_custom_command(OUTPUT cppgir.1
COMMAND ${RONN} --roff --pipe ${CMAKE_CURRENT_LIST_DIR}/docs/cppgir.md > cppgir.1
DEPENDS docs/cppgir.md
WORKING_DIRECTORY .)
add_custom_target(manpages ALL DEPENDS cppgir.1)
endif()
endif () # BUILD_TOOLS
# headers
install(DIRECTORY gi override
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
if (INTERNAL_EXPECTED)
install(DIRECTORY ${EXPECTED_LITE_INCLUDE}/nonstd
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/gi)
endif ()
# doc
install(FILES README.md docs/cppgir.md
DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(DIRECTORY examples
DESTINATION ${CMAKE_INSTALL_DOCDIR}
PATTERN external EXCLUDE)
if (TARGET manpages)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cppgir.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
endif()
# pkgconfig
set(PKG_CONFIG "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc")
set(PKG_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
# configure pkg config file
configure_file("cmake/cppgir.pc.in" "${PKG_CONFIG}" @ONLY)
install(FILES "${PKG_CONFIG}"
DESTINATION "${PKG_CONFIG_INSTALL_DIR}")
# cmake EXPORTS
set(CONFIG_PACKAGE_LOCATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
set(CONFIG_VERSION_NAME ${PROJECT_NAME}-config-version.cmake)
set(CONFIG_TARGETS_NAME ${PROJECT_NAME}-targets.cmake)
set(CONFIG_NAME ${PROJECT_NAME}-config.cmake)
if (INTERNAL_EXPECTED OR NOT expected-lite_FOUND)
set(CONFIG_NAME_IN ${CONFIG_NAME})
else ()
set(CONFIG_NAME_IN ${PROJECT_NAME}-config-deps.cmake)
endif ()
set(TARGETS_EXPORT_NAME CppGirTargets)
if (BUILD_TOOLS)
# generator
install(TARGETS cppgir
DESTINATION ${CMAKE_INSTALL_BINDIR}
EXPORT "${TARGETS_EXPORT_NAME}")
# ignore file
if (NOT BUILD_EMBED_IGNORE)
install(FILES ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE} ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE_PLATFORM}
DESTINATION ${GI_IGNORE_FILE_INSTALL_DIR})
endif()
endif () # BUILD_TOOLS
# headers
install(TARGETS gi EXPORT "${TARGETS_EXPORT_NAME}")
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/cmake/${CONFIG_VERSION_NAME}"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
ARCH_INDEPENDENT
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/cmake/${CONFIG_VERSION_NAME}"
DESTINATION ${CONFIG_PACKAGE_LOCATION}
)
install(FILES cmake/${CONFIG_NAME_IN} RENAME ${CONFIG_NAME}
DESTINATION ${CONFIG_PACKAGE_LOCATION}
)
export(EXPORT ${TARGETS_EXPORT_NAME}
FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/${CONFIG_TARGETS_NAME}"
NAMESPACE CppGir::
)
install(EXPORT ${TARGETS_EXPORT_NAME}
FILE ${CONFIG_TARGETS_NAME}
NAMESPACE CppGir::
DESTINATION ${CONFIG_PACKAGE_LOCATION}
)
# uninstall target;
# intermediate directories are not removed though
if(NOT TARGET uninstall)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake-uninstall.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
IMMEDIATE @ONLY)
add_custom_target(uninstall
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
endif()

21
cmake/external/glib/cppgir/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Mark Nauwelaerts
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

221
cmake/external/glib/cppgir/README.md vendored Normal file
View File

@@ -0,0 +1,221 @@
# cppgir
`cppgir` is a [GObject-Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection)
C++ binding wrapper generator. That is, it processes `.gir` files derived
from GObject-Introspection annotations into a set of C++ files defining
suitable namespaces, classes and other types that together from a C++ binding.
In this way, the plain C libraries and objects become available as native
objects along with (RAII) managed resource handling. The generated code
only requires a C++14 compiler and library (as well as obviously the underlying
C headers and libraries that are being wrapped).
## Installation
The code generator can be built using [CMake](https://cmake.org) or
[meson](https://mesonbuild.com/) and requires
Boost and either [fmtlib](http://fmtlib.net/) or a C++20 std library with format
support (e.g. gcc 13+). While it obviously depends on distribution,
these may be typically be obtained by installing following packages;
libfmt-dev libboost-dev
If it is required to compile the manual page ronn is required to be installed
The following step will provide an additional dependency as a submodule;
git submodule update --init
With all that in place, the usual steps apply, so e.g.
mkdir build
cd build
cmake ..
cmake --build .
cmake --install .
or alternatively
mkdir build
cd build
meson setup . ..
meson compile
meson install
The installed components consist of:
* the code generator executable
* (if not embedded by build option) default ignore files
* common headers required by the generated code and default overrides
The latter is needed when using the generated code, the former only
at generation time.
In case of CMake build, it is also possible to build some examples whose
sources are also installed along with some documentation.
As such, the meson system only builds and install the (most) relevant parts,
as may be useful when used as a subproject.
For either build system, an [example](examples/external) shows how `cppgir`
can be used in a project.
## Release numbering and API stability
The generated code for an even-numbered major version should be (compile) API
stable. If the major version turns odd, then a cycle/series of incompatible
changes may occur, which after actual practice and time can then become
the next even stable version.
## Running
All details can be found in the [manpage](docs/cppgir.md), but a simple
example wrapping [GStreamer](https://gstreamer.freedesktop.org/) libraries is
as follows:
generate --output /tmp/gi GStreamer-1.0
The above (obviously) assumes that suitable `.gir` files are present in
the usual location, as typically installed by a `-dev` package. It is
also assumed the generator has been installed (so "running installed").
Running the generator uninstalled is also possible, but then (if not embedded) the
default ignore files in `data` directory have to explicitly specified as well
(using either option or environment variable as specified in manual).
That's it, all wrapping code is now available in `/tmp/gi` (in a number of
subdirectories). It only requires a C++14 compiler and library and can be
included and compiled into the target library or executable. The latter then
has no additional dependencies other than the plain C libraries (gstreamer and
lower level ones in this case).
## Compilation
The wrapping binding code presents an API along the lines of any other binding,
which also means it maps pretty straight onto the original library API.
So all API is simply found where expected. To use it fully inline,
the following include snippet suffices:
#define GI_INLINE 1
#include <gst/gst.hpp>
Of course, the include path must have been setup properly to contain the output
root of the generated code (i.e. `/tmp/gi` in previous example). It must also
contain the proper path for header code supplied by this repo (using e.g. a
pkgconfig fragment if it has been installed, or an IMPORT target or by other
means).
See the [manpage](docs/cppgir.md) for additional essential remarks on the API
of the generated code or typical non-inline use of the code.
## Examples
The examples (in the so-named directory) illustrate and cover various practical
aspects and use of the generated API. While they are all simple and trivial,
they do illustrate use in various domains such as `Gio`, `Gst` and `Gtk`.
For example, when it comes to traditional C `libgio`, there is usually a choice
between a synchronous (blocking) call and a corresponding asynchronous
(non-blocking) call. The latter is then typically used to establish a chain of
completion handlers. In other settings and languages, it has become customary
to employ async/await operations/keywords. That way, the flow of code remains
linear as in the blocking case. However, some form of callback chain is
typically present behind the (implementation) scenes and a more practical
consequence is that async/await keywords need to be sprinkled in quite some
places (down to call stack depth). That, in turn, is then intrusive/disruptive
in its own way.
Alternatively, one can use so-called stackful co-routines or fibers to maintain
stack context when execution has to be suspended (e.g. awaiting I/O, or some
condition). An example implementation is provided by [Boost
Fiber](https://www.boost.org/doc/libs/latest/libs/fiber/doc/html/index.html)
which caters for (cooperative) switching between multiple fibers on a single
thread. This is viable in C++ since such code should be exception-safe anyway
and as such should not be taken by surprise upon code execution not making it
all the way through a code sequence. After all, that might happen at any time
upon exception, as in the particular case of fiber context destruction in stead
of regular fiber termination. This approach also allows using legacy code in
multiple fibers on a single thread, since no additional synchronization is
required, and no special support (or keyword sprinkling) is needed along the
callstack (down to point of suspension). To come down to it, the async Gio
example provides a `GMainContext` based `Fiber` scheduler to integrate and
support using Fiber in a standard GLib mainloop setup. In particular, that
allows turning `Gio`'s async calls into (apparent) blocking calls when run on a
fiber, which then leads to a concise sequential code flow.
## Features
A few simple but nice to have features of the generated code are:
* well structured and easily understood
* it can be used either fully inline or have implementation code compiled
into one or more object files for subsequent linking
* it allows for extensions/overrides (similar to e.g. PyGObject)
supplied by separate files (so not in the generated ones),
either default ones supplied along with this repo or added custom by
the project using the generated code
Another feauture might be handy even if one is not interested in generated
C++ code. As the `.gir` files as processed, a number of consistency checks
are performed and warned about. In that way, the generator also acts somewhat
as annotation validator, and can typically spot some missing `(out)` or
`(array)` (and related) annotations.
Some other features have been implicitly mentioned above, but are perhaps best
high-lighted by addressing how it differs from [gktmm](https://www.gtkmm.org)
(and the related question; why another C++ interface). To this end, first
note or recall that gtkmm consists of many repositories (glibmm, gtkmm,
gstreamermm, ...), all of which typically also yield a distribution
package for library code and headers. So, when using e.g. gstreamermm, several
such packages are required, either for their headers at compile time or for the
libraries at runtime. In contrast, only 1 repo is needed here, and using the
generated code then incurs no additional (runtime) dependencies other than the
(unavoidable) C libraries.
The code in gtkmm's (and friends') libraries can be considered as hand-crafted.
Well, it is produced based on templates, but those are manually maintained. It
does not use or consider the GObject-Introspection annotations in any way (not
in the least because it predates that system). So, as a C library and API
evolves, the corresponding gtkmm has to be manually synchronized. That is, if
there is at least a corresponding gtkmm one. For a not-so-well-known-or-popular
one there may not be, and definitely not for a custom developed one. On the
other hand, whenever (minimal) annotations are available, you are good to go
with any binding, whether PyGObject or the one provided here.
Since the gtkmm code is hand-crafted API from the ground up, that allows for
some nifty things (such as the signal approach mentioned further below). On the
other hand, sometimes it might be too nifty in that the C++ API does not quite
track or match the original one. Instead, it is then more of an alternative or
parallel one. For example, in GStreamer, there is (only) one (mini-object)
C-type of `GstEvent` (with a type field indicating the precise type of event).
So it is handled this way throughout the API and in bindings (e.g. PyGObject).
However, in gstreamermm it has been chosen to have many types (i.e. subclasses)
of `Gst::Event`. This may well be a natural C++ way (or a Python one if done
with Python classes), but all together it presents an API where things are not
quite where expected, and it is in that regard not a straight binding. In
contrast, the generated code here is as straight as can be, and functions to
call are right where the fingers expect them to be, whether in PyGObject or
here.
So, in the concept of "C++ binding API", gtkmm has emphasis on C++
up to the point of almost a parallel or independent API with quite some
trimming. The latter is illustrated by the separate
[libsigcplusplus](https://github.com/GNOME/libsigcplusplus) (with similar
notions in e.g. [boost signals2](https://github.com/boostorg/signals2)). Here,
however, the emphasis is on binding.
Of course, other than this straight binding, one is still free to use and bring
in any other lib, e.g. one of the aforementioned signal helper libs. However,
in most cases the "straight bindings" will suffice. Moreover, a few small
additional RAII helpers are provided that may be useful. See the examples for
an illustration on how they can be used.
## Limitations and Remarks
A compile-time binding is somewhat different than a typical more runtime script
language binding (e.g. Python). While most of the annotated API is usually
well enough handled and covered for practical use, there are some limitations
and issues to consider. Again, more details and further remarks are provided
in the [manpage](docs/cppgir.md)

View File

@@ -0,0 +1,25 @@
# from https://gitlab.kitware.com/cmake/community/-/wikis/FAQ
# short unix alternative;
# xargs rm < install_manifest.txt
if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
endif()
file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
string(REGEX REPLACE "\n" ";" files "${files}")
foreach(file ${files})
message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
exec_program(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
if(NOT "${rm_retval}" STREQUAL 0)
message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
endif()
else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
endif()
endforeach()

View File

@@ -0,0 +1,29 @@
#include <coroutine>
struct promise;
struct coroutine : std::coroutine_handle<promise>
{
using promise_type = ::promise;
};
struct promise
{
coroutine get_return_object() { return {coroutine::from_promise(*this)}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
static coroutine
f()
{
co_return;
}
int
main(int argc, char **argv)
{
return 0;
}

View File

@@ -0,0 +1,7 @@
#include <format>
int
main(int argc, char **argv)
{
(void)std::format("");
return 0;
}

View File

@@ -0,0 +1,4 @@
include(CMakeFindDependencyMacro)
find_dependency(expected-lite)
include("${CMAKE_CURRENT_LIST_DIR}/cppgir-targets.cmake")

View File

@@ -0,0 +1 @@
include("${CMAKE_CURRENT_LIST_DIR}/cppgir-targets.cmake")

View File

@@ -0,0 +1,9 @@
prefix=@CMAKE_INSTALL_PREFIX@
includedir=${prefix}/include/@PROJECT_NAME@
Name: @PROJECT_NAME@
Description: GObject Introspection C++ wrapper generator.
Version: @PROJECT_VERSION@
# unfortunately, no pkg-config on nonstd-expected
# so no Requires if used as external dependency
Cflags: -I${includedir} -I${includedir}/override

72
cmake/external/glib/cppgir/conanfile.py vendored Normal file
View File

@@ -0,0 +1,72 @@
import os
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
class CppGirConan(ConanFile):
version = "2.0.0"
name = "cppgir"
description = "gobject-introspection C++ binding generator"
license = "MIT Software License"
url = "https://gitlab.com/mnauw/cppgir.git"
exports_sources = "data/*", "docs/*", "expected-lite/*", \
"gi/*", "override/*", "tools/*", "test/*", \
"examples/*", "!examples/external/*", \
"CMakeLists.txt", "cmake/*", "LICENSE", "README.md"
# ignore data depends on OS
settings = "os", "compiler", "build_type", "arch"
build_policy = "missing"
author = "Mark Nauwelaerts"
test_package_folder = "examples/external"
options = {"header_only": [True, False]}
# default host dependency case
default_options = {"header_only": True}
def requirements(self):
if not self.options.header_only:
self.requires("boost/[>=1.58]", options={'header_only': True})
self.requires("fmt/[>=8.1.1]")
def layout(self):
cmake_layout(self, build_folder="build.conan")
# adjust for editable
self.cpp.source.includedirs = ["."]
# self.cpp.build.bindir = "."
def generate(self):
tc = CMakeToolchain(self)
tc.variables["BUILD_EXAMPLES"] = False
tc.variables["BUILD_DOC"] = False
# cmake --install relocation does not play well with ignore paths
# so have those compiled in
tc.variables["BUILD_EMBED_IGNORE"] = True
tc.variables["BUILD_TOOLS"] = not self.options.header_only
tc.generate()
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
if not self.conf.get("tools.build:skip_test", default=False):
test_folder = os.path.join(".")
# test depends on GLib, which may not be present
for t in ["gi-test", "gi-test-17"]:
test = os.path.join(test_folder, t)
if os.path.exists(test):
self.run(test)
def package(self):
"""Run CMake install"""
cmake = CMake(self)
cmake.install()
def package_info(self):
self.cpp_info.includedirs = ["include/cppgir"]
def package_id(self):
# options access not allowed here, unfortunately
if False and self.options.header_only:
self.info.clear()

View File

@@ -0,0 +1,181 @@
# generic
.*_unref
.*_free
.*_ref
.*_ref_sink
# GLib
GLib:constant:LOG_DOMAIN
GLib:record:Error
# deprecated
GLib:function:g_assert_warning
GLib:function:g_slice_.et_config
GLib:function:g_variant_get_gtype
GLib:function:g_thread_init_with_errorcheck_mutexes
# GVariantIter apparently represents 2 distinct types;
# + a C-boxed type (used with _init)
# + a heap-based behaving like GBoxed (with _copy and _free)
# however, annotations do not cover/mention anything of the latter
# so drop those parts here
GLib:method:g_variant_iter_new
GLib:method:g_variant_iter_copy
# too tricky with generic signature
GLib:method:g_source_set_callback.*
# annotation problem
GLib:function:g_strv_contains.*
GLib:function:g_unichar_to_utf8
# not needed; cause trouble otherwise
#GLib:record:ByteArray
#GLib:record:Bytes
GLib:record:PtrArray
GLib:record:Array
GLib:record:S?List
GLib:record:HashTable
GLib:record:HashTableIter
GLib:record:Queue
# annotation problem
GLib:VariantType:string_scan
# conditional constants
GLib:constant:macro__has_attribute.*
GLib:constant:.*GETTEXT_DOMAIN
GLib:constant:WIN32.*
# not defined for C++ compiler
GLib:constant:.*C_STD_VERSION*
# GObject
GObject:record:Value
# deprecated
GObject:record:ValueArray
# GIO
# deprecated
Gio:interface:DesktopAppInfoLookup
Gio:method:g_notification_set_urgent
Gio:method:g_settings_list_keys
# annotation problem
Gio:virtual-method:ask_question
# not covered by header includes
Gio:function:g_networking_init
# external plugin module API
Gio:method:g_io_module_(load|unload)
Gio:function:g_io_module_query
# private parts of the above; these should not make into the GIRs
# but they might if gobject-introspection was built with embedded glib (meson wrapper)
GModule:constant:MODULE_IMPL_.*
GLib:constant:TRACE_.*
GLib:function:trace_.*
GLib:function:set_prgname_once
Gio:function:to_rrtype
Gio:class:ThreadedResolver
GObject:bitfield:IOCondition
# Gst
Gst:constant:ERROR_SYSTEM
Gst:callback:DebugFuncPtr
# GstBase
# actually macros, but with gtk-doc comment
GstBase:method:gst_byte_writer_put_buffer
GstBase:method:gst_bit_writer_get_remaining
# missing G_BEGIN_DECLS in video-blend.h header in some versions
# (leads to C/C++ link mismatch)
GstVideo:function:gst_video_blend_scale_linear_RGBA
GstVideo:function:gst_video_blend
# likewise missing G_BEGIN_DECLS in gstaudioiec61937.h
GstAudio:function:gst_audio_iec61937_frame_size
GstAudio:function:gst_audio_iec61937_payload
# GstNtpClock is child of GstNetClientClock
# but it uses the same C struct type
GstNet:class:NtpClock
# Gtk and lower layers
#
# deprecated
GdkPixbuf:record:Pixdata.*
GdkPixbuf:bitfield:Pixdata.*
GdkPixbuf:constant:PIXBUF_MAGIC_NUMBER
GdkPixbuf:constant:PIXDATA_HEADER_LENGTH
# wrong annotation
cairo:function:image_surface_create
# repeated as GdkRectangle
cairo:record:RectangleInt
# from generated code
Pango:record:ScriptForLang
# in a header without extern C guard
Atk:function:get_major_version
Atk:function:get_minor_version
Atk:function:get_micro_version
Atk:function:get_binary_age
Atk:function:get_interface_age
# private
Gdk:method:destroy_notify
Gdk:function:synthesize_window_state
# xlib GIR does not specify header
# and including that one makes things really messy
# (due to all sorts of define's)
xlib:.*
# recent GIR describes way more than it specifies headers
# also pretty low level, so let's sidestep altogether
HarfBuzz:.*
# likwise so for freetype2
freetype2:.*
# Gsk
# deprecated
Gsk:class:GLRenderer
# in separate gtk4-broadway.pc
Gsk:class:BroadwayRenderer
# likewise filter out some related GtkX parts
# (should be in a separate ns btw for good measure)
Gtk:class:Plug
Gtk:class:Socket
# private
Gtk:method:gtk_widget_path_iter_add_qclass
# missing in summary header gtk-a11y.h
Gtk:class:HeaderBarAccessible
Gtk:class:FileChooserWidgetAccessible
# generated dbus sekeleton parts made it into GIR but otherwise missing
Gtk:record:_MountOperation.*
# Gtk4
# header not part of included headers
Gtk:constant:IM_MODULE_EXTENSION_POINT_NAME
# Gtk 4.9.1 deprecates TreeView and Cell Renderers
# alternatives appear not so binding-friendly
# so arrange to not discard these
deprecated:Gtk:4.0
# misc config; specify substitute c:type for GtkSnapshot (defined in override)
#!ctype:Gtk.Snapshot:GI_PATCH_GtkSnapshot
# signal emission method wrappers
# undocumented; and should not be wrapped either
# (with some problematic parameter types)
Soup:method:soup_message_content_sniffed
Soup:method:soup_message_wrote_chunk
Soup:method:soup_message_wrote_headers
Soup:method:soup_message_wrote_body
Soup:method:soup_message_wrote_informational
Soup:method:soup_message_got_chunk
Soup:method:soup_message_got_headers
Soup:method:soup_message_got_body
Soup:method:soup_message_got_informational
Soup:method:soup_message_starting
Soup:method:soup_message_restarted
Soup:method:soup_message_finished
# deprecated interface since 13 years
Soup:interface:PasswordManager
# private
Soup:function:soup_get_resource
# unstable API
Soup:class:Requester$
Soup:method:soup_websocket_extension.*
Soup:enumeration:RequesterError

View File

@@ -0,0 +1,5 @@
# items to ignore on UNIX platform
GLib:.*:.*WIN32.*
GLib:.*:g_win32.*
GLib:enumeration:Win32.*

View File

@@ -0,0 +1,3 @@
# items to ignore on WIN platform
GLib:.*:.*g_unix.*

View File

@@ -0,0 +1,791 @@
cppgir(1) - GObject-Introspection C++ binding wrapper generator
==================================================================
## SYNOPSIS
`cppgir` [OPTION...] `--output` _DIRECTORY_ GIR...
## DESCRIPTION
`cppgir` reads each of the specified GIR and converts these (and any
dependencies) into C++14 wrapper code that collectively then make up a
'binding' (in
[GObject-Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection)
terminology). Each GIR can be specified as a full pathname to the `.gir` file
or simply by the basename (i.e. no path or `.gir` suffix), with or without
version. Of course, in the latter case, the `.gir` must be in a standard
location, or other options must specify additional whereabouts.
## OPTIONS
See [BACKGROUND](#background) later on for further details on some of the
concepts used in the following descriptions.
* `--output` _DIRECTORY_:
Specifies the top-level directory in which to generate code.
It will be created if it does not yet exist.
* `--gir-path` _PATHS_:
Adds a colon-separated list of additional directories within which
to (recursively) search for a `.gir` file (if not specified by full pathname).
* `--debug` _LEVEL_:
Debug level or level of verbosity, higher numbers are more verbose.
* `--ignore` _FILES_:
Adds a colon-separated list of so-called ignore files.
* `--suppression` _FILES_:
Adds a colon-separated list of so-called suppression files.
* `--gen-suppression` _FILE_:
Specifies a suppression file to generate during this run.
* `--class`:
Requests generation of implementation class code needed for subclassing.
* `--class-full`:
Requests generation of a plain as-is C signature fall-back method for
an otherwise unsupported unwrapped method. Only applicable if
`--class` is also specified. It also requires use of the latest custom
subclass (signature) approach (see below for details on that),
as these plain methods are not "activated" in case of legacy approach
(for backwards compatibility).
* `--expected`:
Use an error return type based on [std::expected](http://wg21.link/p0323) proposal
(as opposed to throwing exception).
* `--dl`:
Use dlopen/dlsym to generate (most) calls rather than usual "direct" calls.
As such, a great many calls might then fail at runtime. So, if combined
with `--expected` all those calls will use the above error return type.
* `--const-method`:
Mark (almost) all generated methods in generated wrappers as `const`.
Alternatively, perhaps more recommended, see also the helper `gi:cs_ptr` type.
* `--class-args` _MIN_OPTIONAL_:
If >= 0, minimum number of non-required arguments that triggers generation of a
`CallArgs` signature variant (see below for details).
* `--basic-container`:
Also generate a collection signature for an input collection of basic type
(e.g. `int`, etc). See below for some details and discussion.
* `--output-top`:
Also generate convenience `.cpp` and `.hpp` files in root output directory
per namespace, as may be useful for some build tool setups.
* `--dump-ignore`:
(only if compiled with embedded ignore) Dumps embedded ignore data.
## ENVIRONMENT
In stead of command-line options, environment variables can also be used. Note,
however, that options are still taken into account even when variables have
been set. The following environment variables are considered, and have the same
meaning as the corresponding command-line option:
`GI_DEBUG`, `GI_IGNORE`, `GI_SUPPRESSION`, `GI_GEN_SUPPRESSION`, `GI_OUTPUT`,
`GI_CLASS`, `GI_CLASS_FULL`, `GI_EXPECTED`, `GI_DL`, `GI_GIR_PATH`
In addition to the above, `GI_GIR` can specify a colon-separated lists of GIRs
(specified as on command-line). `XDG_DATA_DIRS` is also used as additional
source of directories to search for GIRs (within a `gir-1.0` subdirectory).
## BACKGROUND
### API v2
Note that v2 API is somewhat different than previous API, so some porting of
existing code may be needed. See also later section for a rationale and
discussion on changes.
The generated code provides a straight binding as specified by the annotations,
so everything is pretty much where expected, such as methods within classes
in turn within namespaces. For example, all `GObject` types are within
namespace `gi::repository::GObject`. With that in mind, it should be easy
to use and navigate in generated code, along with following comments:
* As customary, anything within a `detail` or `internal` namespace is not meant
for public use and subject to change. The top-level gi namespace defines
a few things that make up public API which is meant to be stable
(though at this stage of maturity no full guarantee is provided).
* Some generated code may have `_` (underscore) appended to it simply to avoid
clashing with a reserved keyword (or a preprocessor definition). It has
no special (reserved) meaning otherwise.
* However, anything with leading underscore (if encountered) should be considered
as internal (and not meant for public API).
In overall, the generated code is very lightweight and clear, easily understood
and with little runtime overhead, as also illustrated by the following
overview of wrappers for various kinds of types. Note that almost all of
them essentially wrap a pointer and therefore should be checked for validity
prior to many uses as with any "smart pointer"
(e.g. using provided `operator bool()`).
**Objects.**
A GObject is a single pointer along with class code that manages a single
refcount (including decrement upon destruction). The refcount it manages is
either received/taken from a `full` transfer, or `ref_sink`'ed (in case of
`none`/`floating` transfer, see also discussion in subsequent section on the
intricacies of the latter and theoretical edge cases).
**Boxed Types.**
Similarly, but with a minor twist, wrappers for a boxed GType `MyBox` come in 2
kinds; an owning `MyBox` and a non-owning `MyBox_Ref`. In both cases, the
wrapper is again a single pointer with some suitable/applicable helper methods.
The former essentially acts a "unique ptr" (with `g_boxed_free` deleter) whereas
the latter acts as a "naked ptr/reference" (without any ownership or cleanup).
Obviously, for the latter case, all the usual caution regarding dangling
references (etc) applies. The latter are used for transfer `none` cases and
the former in transfer `full` situations. In case a safe "reference" needs to
be kept around (e.g. in some member), then a `_Ref` can be `.copy_()`'d (which
uses `g_boxed_copy`) to an owning wrapper. The above semantics also imply that
the owning wrapper is move-only (and again `.copy_()` yields a copy). However,
there are quite some cases where a boxed copy is based on a refcount (which also
preserves the box identity/pointer). Those cases have been specially marked (in
overrides) to make the owning wrappers copyable as well. Likewise, a `_Ref` of
such cases can be (implicitly) assigned/copied to an owning one (in each case
triggering a `g_boxed_copy` which is then known to be plain and cheap).
If desired, additional wrappers could be marked as copyable, in which case a
wrapper copy invokes a potentially more expensive (and non-identity preserving)
`g_boxed_copy`. Also, or alternatively, if `GI_ENABLE_BOXED_COPY_ALL` is
defined and truthy, then all boxed wrappers are copyable in that way.
**Record Types.**
Plain records (i.e. structs with no registered GType) are handled in a similar
fashion, with `g_free` as "deleter" (and without any copy support). Since no
lifecycle resource management (construction, destruction) is available for such
types, there are (quite some) limitations to what code generation or binding can
do here (see also discussion in corresponding section).
**Strings.**
A string (e.g. `char*`) is also regarded and wrapped in a similar way. That is,
a `gi::cstring` wraps (and owns and manages) a C `char*` and `gi::cstring_v` is
the corresponding non-owning variant. Obviously, the former bears resemblance
to `std::string` whereas the latter to `std::string_view`. In fact, as there is
no real definitive "string API" (in C or glib), their API is fairly similar
(though not guaranteed identical) to the `std` counterparts. Also, various
conversions from/to `std` counterparts should allow for convenient type
interchange. Additional integration with other string types is also possible by
further specialization of `gi::convert::converter` (see `gi/string.hpp` source
for details).
**Collections**.
That is, `GList`, `GSList`, `GPtrArray`, `GHashTable` or plain arrays
(zero-terminated or not). Similar to `std` container, each collection wrapper
is a templatized `gi::Collection` type, with (a.o.) a type parameter for the
contained type. As with some of the above types, such wrappers come in an
owning and non-owning variants, as specified by another (type) parameter and
obtained from annotations, i.e. transfer `none`, transfer `container` or transfer
`full`. Note that the "ownership" specifies both ownership of the container and
of the contained elements. Of course, where needed, code generation will select
and specify the proper type (e.g. as function parameter). Following aspects
are worth mentioning;
* Templatized constructors and conversion operators support construction
from/of and assignment from/to (e.g.) `std` container types. Likewise
so for "similar" (duck-ed) types, where "similar" refers to member types and
constructor signatures.
* A (`std`) container-ish API is also provided, though neither identical
nor fully compatible (a.o. due to limitations of the C wrappee's API).
However, the `none` (ownership) variant is considered read-only and so
it does not provide any "modification" API parts and only a `const` iterator.
As almost no wrapper methods are `const`, an `auto p : coll` (range-for)
pattern is recommended (wrappers are cheaply copied). Other variants do
support modification as well as iteration that allows for a `auto &p : coll`
pattern (if so desired). In particular, this applies to the `full` variant,
which is the recommended one for "standalone" use (as container), as it
safely manages ownership of both itself and elements.
* Wrappers of refcounted collections (`GPtrArray`, `GHashTable`) are
otherwise similar to object wrappers. So they *always* manage a refcount (and
are copyable) regardless of ownership variant (none, etc). The other wrappers
are similar to boxed wrappers, e.g. copyable in `none` variant, but otherwise
assume unique ownership and are non-copyable.
* A `gi::CollectionParameter` may also used by code generation for a function
input parameter. In case of `none` ownership, this type/instance will
temporarily hold ownership of a collection that may be created by conversion
from another container. Temporarily here refers to the duration of the call
during which the parameter instance exists. It is not (and should not be)
used elsewhere.
* For an input collection parameter of basic type (e.g. `int`), the
original C signature is typically used. That is, 2 parameters (`int*` and
`gsize`). The rationale here is that the same signature may also occur for an
output collection (of specified input size). Preserving the original signature
ensures that it can be used whether or not the annotation is correct. The
latter may not be the case as these APIs are typically "low-level" (e.g. involve
some buffers), and as such are often not considered by "scripted binding". It is
also an efficient and clear API as any buffer's "location" (e.g. `std::vector`
or otherwise) can easily be provided by means of these 2 parameters.
However, if desired and specified by `--basic-container` option, then an
additional collection-based signature is generated as well.
In short, one can choose to work with `std` types and convert to
collection wrappers upon function call/return, but for simple cases (or beyond),
the collection wrapper might well serve (without conversion).
**Plain Types.**
Various enum, (static) method, functions, typedef (for callback) fill in the
rest.
**Functions.**
Functions that involve the usual `GError` return pattern are wrapped in a few
ways. On the one hand, in a straight way, where the error is a (wrapped error)
output parameter. Alternatively, the error parameter is removed from the
signature. In that case it is "returned" by either throwing the (wrapped) error
(which is also a `std::exception` subclasss), or by returning a suitable
`expected` type (with the wrapped error type as error type). While throwing is
default behaviour, the latter can be requested using `--expected` option.
In case of a `GError` in (function) callback or virtual method signature, it is
always retained as a (wrapped) error output parameter and preferably used to
report an error that way. Alternatively, an exception can be thrown, preferably
then a `GLib::Error` instance. Callback wrapping code will catch any exception
and report (to `C` caller) using `GError` output along with a zero-initialized
return value, which is likely but not necessarily a good choice.
Note, however, that the aforementioned `catch` only applies if exception support
is enabled. Auto-detection of this should usually work, but if needed can be
specified by defining `GI_CONFIG_EXCEPTIONS` explicitly (truth/falsy).
If a function has (non-GError) output parameters, then there is a signature
where these outputs are parameters (as in the plain C case) and another variant
where these are incorporated in the return value, which may then become a
`std::tuple<>` type.
If so configured, some so-called `CallArgs` variations may also be generated. In
this case, (roughly) a custom `xyz_CallArgs` struct type is generated (for each
function `xyz`) with members corresponding to the function arguments along with
a function with 1 argument (of that custom struct type). In case of many
(optional) arguments, this argument could be specified using designated
initializer syntax, thereby allowing a sort-of "call by keyword". Again, there
are variations with output parameters as return value or not. Besides some
potential advantages, there are also some drawbacks, however. First and
foremost, when using the member names in designated initializers, these names
then become essentially part of the API, although the name's origin as
plain C function parameter does not provide stability guarantee. Also,
suffice it to say a great many struct types can be generated this way. This could
be mitigated by employing a suitable "generation level", e.g. 2. In that case,
only functions that have at least 2 (or more) non-required arguments will have
such a custom type and signature generated.
**Subclasses and Interfaces.**
Some additional specifications on how subclasses and interfaces are mapped
may also be in order. A subclass in the GObject world is directly mapped as a
subclass in the C++ binding. However, if a GObject implements an interface, the
generated class does not inherit from the interface's (generated) class. This
is mostly of a matter of implementation choice (and to ensure its lightweight
simplicity). However, knowledge of implemented interfaces is not always
available at compile time, e.g. in case of dynamically loaded GStreamer
elements (though it is more likely in case of Gtk hierarchy). Since there would
be no inheritance in the dynamic case, a consistent choice is not to have it at
any time. However, for ease of use, some helper code is generated when an
implemented interface is known at generation/compile time, as illustrated in
the following snippet from an example
// use a cast if not known, either to a class or interface
auto bin = gi::object_cast<Gst::Bin>(playbin_);
// known at compile time; overloaded interface_ method
auto cp = bin.interface_ (gi::interface_tag<Gst::ChildProxy>());
### SUBCLASS IMPLEMENTATION API
There may be times when one would want to make a custom subclass of GObject, or
of some Gtk widget. In the same vein, (current) implementation choices imply
that one should not simply inherit from `Gtk::Window`. Part of the motivation
here is that such subclassing depends on style and setting, i.e. it is rather
rare when in a GStreamer setting, but less so in e.g. Gtk. As such, the
possibly rare cases should not burden or complicate the basic wrapping usecase.
Before going into the details of "how", let's first consider what a "subclass"
actually means in this context. It will probably involve a (C++) (sub)class of
some generated class (related to a GObject class type) with potentially
extra/custom properties and signals. In particular, the latter implies it also
involves a new `GType` that defines a subtype (of the aforementioned GObject
class type). An instance of such class/type then consists of a C++ instance
that is 1-to-1 associated with a GObject instance of the custom defined (sub)
`GType`.
In turn, this leads to (at least, for now) 2 possible ways to create instances;
* triggered on C++ side, in the usual way through a constructor. The bottom
of the constructor chain registers a custom `GType` (if still needed), creates
an instance (`g_object_new()`) and associates it (with the C++ instance).
The advantage is that standard use of C++ constructor applies. The downside
is that the defined `GType` can not be (safely) used in the GObject
(eco)system, as any `g_object_new()`'d instance is incomplete (as it lacks a
C++ instance).
* triggered on C-side by `g_object_new()`. In this case, the registered type's
custom (GObject) `constructor` first delegates to parent constructor to create
a GObject subtype instance and then `new`'s a C++ object, and again the
bottom of the constructor chain associates it with created GObject instance.
In this case, an instance has to be created based on `GType`. However, this
type can be safely used in any `GType` based factory system, e.g. when
referenced (by name) in XML/UI file or (by number) in a GStreamer plugin
factory.
In summary, (up to) 2 different ways to create objects, and so (up to) 2
different custom `GType` that can be defined by a "subclass". The latter one
is more recent and is also the recommended approach as instances are always
properly created. Also, if desired, some additional helper `new_()` member
can be defined that acts as a surrogate constructor.
So, how to subclass then? By a slight twist by using the `impl` namespace
variations, as in following excerpt from an example:
class TreeViewFilterWindow : public Gtk::impl::WindowImpl
{
// ...
public:
// Assume (hypothetically) that Window also implements FakeInterface
// with a set_focus method, then a compilation failure will be triggered (as
// it can no longer be detected whether set_focus is defined in this class).
// Then the following inner struct is needed to resolve so manually;
struct DefinitionData
{
// the last parameter specifies whether the method is defined
// (which may well be false in all class/interface cases if not defined)
GI_DEFINES_MEMBER(WindowClassDef, set_focus, true)
GI_DEFINES_MEMBER(FakeInterfaceDef, set_focus, false)
};
// NOTE for the auto-detection to work, the methods must be accessible
// so either they should be defined public, or (e.g.) WindowClassDef
// must be declared friend, or the above manual resolution can be used.
// this (super)constructor signature leads to a C++ side type
TreeViewFilterWindow () : Gtk::impl::WindowImpl (this)
{
// ...
}
// this (super)constructor signature leads to a C-side type
// NOTE InitData is not (easily) instantiated,
// so this constructor is only used by the internal C-side mechanics
TreeViewFilterWindow (const InitData &id)
: Gtk::impl::WindowImpl (this, id, "TreeViewFilterWindow")
void set_focus_ (Gtk::Widget focus) noexcept override
{
}
// the above constructor is also re-used during (C-side) type registration
// (in that case with an "empty" id and no associated GObject setup)
// that can avoided by providing a separate ...
GType get_type_()
{
// no interfaces, properties or signals to declare
return register_type_<TreeViewFilterWindow>("TreeViewFilterWindow", 0, {}, {}, {});
}
};
// create an instance of (either) type
// it prefers the latter C-side type, if supported, and supports extra arguments
// (a second template parameter allows for specific selection,
// see code comments and examples for details)
gi::make_ref<TreeViewFilterWindow>()
Parent (class or interface) methods can then be overridden or implemented
in the usual way by simply defining them in the subclass. It is also possible
to define custom signal and properties in the subclass, as illustrated in the
`gobject.cpp` example. As mentioned, the inner `DefinitionData` struct in the
above fragment is usually not needed, but only in case of conflict/duplication
of class/interface member(s).
Since this is considered an optional feature, the `impl` parts are not generated
by default, but only if the `--class` option is specified. Since the virtual
methods share some similarities with callbacks they are also subject to some
limitations (see corresponding section). As such, it may happen that some
virtual methods do not have a wrapper. If the `--class-full` option is
specified, then a passthrough virtual method (with C signature as-is) is then
generated instead, which can then be overridden and implemented as a fallback.
So the custom type registration (that happens behind the scenes) can then still
be used, albeit at the expense of dealing with a plain C signature and types
(which is similar to directly calling a C function as a fallback if no wrapper
function was generated for some reason).
It is also a fairly advanced feature, with various aspects not immediately
obvious. For example, the resulting "instances" have "2 sides"; there is C++
object instance as well as an associated (derived) GObject instance (which
internally refer to each other in 1-to-1 association ). In particular, it
follows that their destruction must be closely coordinated. This is potentially
tricky as the GObject side is traditionally reference-counted, whereas the C++
object could be anything (stack-allocated or otherwise). The latter can be
stack-allocated if it is ensured that there is no lingering reference in the
GObject world. So, it depends on the use-case as to how to proceed. But in
overall, it is probably recommended to manage lifetime and ownership based on
GObject (side) reference count. The `gi::make_ptr` and `gi::ref_ptr` helpers
can be helpful in this regard.
There are also situations where the C API (implementation) involves some
"low-level tricks" which do not port over in a simple or straight way (e.g.
`GtkBuilder`). Those are likely in need of some custom overrides or extensions
(see also below) which may or may not already be provided for some particular
API/situation. It is highly advised to browse provided overrides and
extensions and/or consult the closest relevant related example
(which usually showcases what is available, along with additional explanations).
### CODE LAYOUT AND BUILD SETUP
The generated code is written to the top-level with the following layout.
Each GIR namespace has a corresponding subdirectory, say `ns`
(and also a C++ namespace, `cppgir::repository::ns`). The top-levels
headers for a namespace are then:
* `ns.hpp`:
a regular header providing the namespace's declarations.
It will also include the dependent namespaces' top headers.
If the macro `GI_INLINE` is defined, then it will also include ...
* `ns_impl.hpp`:
contains the definitions corresponding to the declarations.
Normally, this would be a `.cpp` file, but as they might be included directly
in the inline case, they have been named `xxx_impl.hpp` instead.
* `ns.cpp`:
this merely includes `ns_impl.hpp` and is as such no different
than the latter, except for more traditional naming.
Compiling this file in the non-inline case provides all the definitions
for the namespace in the resulting object file.
* `*.cppm`; module wrappers, see next subsection for details.
So, in summary, it comes down to setting up the build system to build each of
the namespaces' `.cpp`, as is also done in this repo's CMake build setup.
There is one other shortcut build setup that is illustrated by the `gtk-obj.cpp`
example file, which includes all definitions (recursively):
#define GI_INCLUDE_IMPL 1
#include <gtk/gtk.hpp>
Note, however, this is only possible if there is exactly 1 top-level namespace,
as doing this for several namespaces will lead to duplicate definitions.
Some items (functions, types) may be marked as deprecated (in source code).
while still present in GIR data. Wrappers will still be generated and
`pragma` are issued to avoid warnings that might otherwise occur.
Generic `gi` support tries to avoid using deprecated code. There is, however,
one exception regarding the use of `g_object_newv`, which is deprecated
but may have to be used if support for an older GLib is required.
This can be arranged by defining `GI_OBJECT_NEWV` (and the deprecation
warning should also be silenced when dealing with newer version).
If the items are also marked deprecated in GIR data, then these are skipped
by default. However, if the string `deprecated:<NAMESPACE>:<VERSION>`
matches (a regexp) in specified ignore data/files, then deprecated items
will be considered for the namespace in question, after being checked as
usual against the ignore list.
If you have specified the `--class` option, then the generated code will
possibly contain classes that inherit from several classes (representing
interfaces). Since various interfaces may have overlapping member names, this
might trigger compilation warnings. These are not suppressed by default, as you
may need to be made aware of this. However, if it does no harm in your
particular case, then defining `GI_CLASS_IMPL_PRAGMA` should arrange for proper
suppression.
The generated code may be quite extensive and so it may present a "heavy build".
Other than that the above allows for a number of different build setups,
`cppgir` tries not to impose any particular approach. In particular, standard
tried-and-tested build optimizations can be applied, such as precompiled headers
(with some good results, as reported in
[issue #99](https://gitlab.com/mnauw/cppgir/-/issues/99)).
**C++ Module support**
At this time of writing, module support is still new-ish in compilers
and (not in the least) build tools. Unfortunately, all major compilers also
take a different approach in handling these wrt command-line argument, source
file extension or binary output. Note that this also likely complicates any
`compile_commands.json` based tooling (e.g. LSP server as used by IDEs) along
with other issues as outlined in
[this post](https://nibblestew.blogspot.com/2023/12/even-more-breakage-in-c-module-world.html)
and originally raised
[long ago](https://vector-of-bool.github.io/2019/01/27/modules-doa.html).
By comparison, precompiled headers are long since well supported, so these may
be a more recommended alternative approach.
Failing that, a next natural fit might be "header units" (in module spec sense).
Unfortunately, compiler setup here varies somewhat and build tool support may be
limited.
So, a next step is a pure/real module. Unfortunately, "core gi" can not be
provided in that form;
* in addition to code, it also "exports" some macros, which can not really be
`export`'ed or `import`'ed (only from a "header unit")
* the core is somewhat intertwined with basic types from GObject,
e.g. some code-generated types are forward declared (which satisfies
for their purposes in "core gi"). However, forward and real declaration
can not pass module boundary.
But "core gi" combined with (generated) GLib and GObject can be wrapped
in a module (along with a separate "macro API header", `gi_inc.hpp`).
Again, `cppgir` does not impose a particular module layout/setup, but provides
the basic parts and pieces to do so. The generated code also provides a few
example module wrappers (with no stability guarantee), also see `gst.cpp` and
`gtk.cpp` examples for possible usage details;
* `ns.cppm` (exports `gi.repo.ns`);
module that wraps/provides `ns` (inline) code, so a typical compilation
produces `.o` code of `ns` and a BMI/CMI that exports `ns` declarations
* `ns_rec.cppm` (exports `gi.repo.ns.rec`);
module that wraps/provides `ns` and all (recursive) dependencies, so a
typical compilation produces `.o` code of `ns` and dependencies, along
with a BMI/CMI with corresponding declarations.
The above have been tested with gcc-15 and clang-18 with some varied measure of
success (apparently, GCC fails to link the non-recursive approach). They each
come with an extra "knob"; if `GI_MODULE_EXTERN` is defined, then the module
purview is essentially `extern "C++" { ... }`. So all declarations are then
attached to global module, as opposed to the named module, and as such do not
incur any modified ABI linkage (`@M`) (so it may be recommended).
Other variations are likely possible, e.g. module partitions, separate
module implementation, etc.
### OVERRIDING OR EXTENDING
It is possible to add functions or methods or override existing names (by
effect of name hiding). To this end, the generated code contains various
'optional include hooks' using the `__has_include` directive. This way, code in
externally supplied (include) files can be inserted into the class definition
chain. There are roughly 3 such 'hook points':
* **initial setup**:
this part is (conditionally) included before the namespace's C headers are included.
This allows specifying define's to tweak subsequent headers or to add
headers that also need to be include'd, and which may not have been specified
in the GIR.
* **class definition**:
these hooks allow extending the wrapped class with new or tweaked methods
* **global extra definitions**:
these are included after all generated code, and supports adding of new global
functions, typedef's, type trait helper declarations, ...
The reader is invited to examine the default overrides in this repo as well as
the generated code to see how this fits together based on a simple naming
scheme and use of macros. In particular, see the provided `GLib` overrides.
Suffice it to add that the `_def` suffix refers to 'default' as supplied by
this repo and which are installed alongside the common headers. The
corresponding non-suffixed filenames should be used by project specific custom
additions.
### CODE GENERATION
It might be necessary to exclude a GIR entry from processing, either because it
is a basic type handled by custom code (e.g. `GObject`, `GValue`, ...) or
because of a faulty annotation. The latter can be a glitch in the annotation
itself, or one that actually refers to a symbol in a non-included private
header. The exclusion can be directed by so-called ignore files, and at least
one such is supplied as a system default ignore containing known and essential
cases to exclude (and without which code generation would not produce valid
code). Such a file consists of lines of regular expressions (`#` commented
lines are ignored). At generation time, each symbol is turned into a
`<NAMESPACE>:<SYMBOLKIND>:<SYMBOL>` string, and excluded if it matches one of
the lines' regular expression. So, for instance, `GObject:record:Value`
prevents processing of `GValue`, since there is already special-case code for
that in the common header code. Further expression examples are found in the
default ignore file. Additional files can be specified by the `--ignore`
option.
As each entry is processed, some notification may be given regarding a
perceived inconsistency in an annotation or an unsupported case (see also [BUGS
AND LIMITATIONS](#bugs-and-limitations)). When the reported cases have been
(manually) checked and considered harmless, the corresponding notices can be
suppressed by specifying suppression files to `--suppression`. The format of
such files is the same as ignore files, except that a match then simply serves
to decrease reporting verbosity. Such a file could be hand-crafted, but it can
also be auto-generated by a run when specifying `--gen-suppression`.
Besides excluding problematic GIR parts, one might also consider solutions to
some problematic GIRs used by other projects, such as fixed GIRs maintained by
[gtk-rs](https://gtk-rs.org/gir/book/tutorial/finding_gir_files.html#gtk-dependencies)
in the referenced [repo](https://github.com/gtk-rs/gir-files).
### (RATIONALE OF) v2 CHANGES
Consider the following python session using gobject-introspection:
>>> import gi
>>> gi.require_version('Gst', '1.0')
>>> from gi.repository import Gst
>>> Gst.init(None)
>>> c = Gst.caps_from_string('video/x-raw')
>>> c.get_structure(0)
<Gst.Structure object at 0x7fe284096760 (GstStructure at 0x1bb4420)>
>>> c.get_structure(0)
<Gst.Structure object at 0x7fe2840b5d00 (GstStructure at 0x1bb43a0)>
What happens here? A different `GstStructure*` is created each time, even
though the same one is returned (by C code) in each case. The python binding
here has no other choice than to use `g_boxed_copy()` on the transfer `none`
return value. If it would not, it would be carrying around an unguarded/unowned
and hence potentially dangling pointer (in some `PyObject` wrapper), which is
a definite no-go in a scripted setting that must always ensure valid objects.
v1 API followed a similary "scripted" style approach where all objects/pointers
should always be safe and valid, with (roughly) `std::shared_ptr` in place of
`PyObject`. Of course, also then with similar (copy) effects as in the above
excerpt and in e.g. [issue #32](https://gitlab.com/mnauw/cppgir/-/issues/32).
v2 now follows a different approach. After all, C++ is much closer to C, and it
is customary to mind about (potentially dangling) references and such, and where
and how (not) to use e.g. `std::string_view`. And so while types/objects are
now no longer always "owning" (and as such always safe), the type conventions do
clearly specify whether or not they do (own). As such, standard C++ practices
should handle what v2 API provides, while avoiding superfluous and potentially
surprising copies or any other "automagic". In particular, the v2 bindings
are therefore even more "tight and direct" than before, with a typical wrapper
being only a cast away from the wrappee (and matching in size and semantics).
**Migration.**
In practice, only limited changes have been needed in the included examples.
Of course, your mileage may vary, depending on usage of "boxed types" as well
as use of (type deduction) `auto` versus explicit type specification.
Some `_Ref` types may have to be used instead here or there, as well as possibly
some `std::move` on "owning" variants (unless overall boxed copy is enabled).
For reasons of consistency and to avoid collision with generated methods,
some more "custom methods" may have had `_` appended
(e.g. `CBoxed::allocate_()`).
## BUGS AND LIMITATIONS
The generated code's coverage is pretty good and comfortably serves most
cases that arise in practice as also illustrated by the examples.
Nevertheless, the following should be mentioned:
**Callback types.**
Only callback types that have an explicit `user_data`
parameter are supported. That includes (fortunately) cases such as connecting
to a signal, or a `GstPadProbeCallback`, though a `GstPadChainFunction` is
excluded. The reason is a technical one; the `user_data` parameter is used to
pass data used by callback wrapper code. A typical (script) runtime binding
handles this using [libffi](https://github.com/libffi/libffi)'s closure API. In
effect, a little bit of executable code is then generated at runtime, and the
address of that code then essentially serves as surrogate `user_data` that can
carry extra meta-data for use by the runtime. This could also be employed here
to lift the `user_data` limitation, it would take a bit extra work, but would
more importantly then also incur an additional dependency.
**Callback handling.**
Even if `user_data` is present, other aspects of a callback signature
may not be supported (at this time), e.g. certain (sized) array parameters.
However, few (if any) of such actual cases are known at this time.
Note that both signals and virtual methods are somewhat similar to a callback
and as such share similar limitations.
Whereas the above items could (in theory) be resolved, the following are more
inherent limitations (by the very context and nature of e.g. annotations).
Fortunately, though, the practical impact is fairly limited (if any).
**const handling.** In C++, this is a Bigger Thing. For instance, a simple
'getter' should preferably be marked const. However, on the original C-side of
things, only very limited consideration is given to this. Even if there is some
`const`, it is not treated with all that much respect, e.g.
`g_value_take_boxed` starts `const` but it is merrily cast away along the way.
As such, there is not much to find on const-ness in annotation data, and so no
point in inventing any. Rather, the focus is simply on getting the proper
function calls done along with automagic refcount and resource management (much
as any runtime binding would do, with no regard for const whatsoever in that
case).
In particular, methods are usually not marked `const` either, as there is
similarly no (semantic) data to decide either way. As such, it is not
recommended to use `const` wrapper types. However, they may arise due to generic
templates or when captured in a lambda. In such cases, the helper (template)
type `gi::cs_ptr` may be useful, or alternatively one might set the option to
mark all code-generated methods as `const`.
**Floating (into darkness).**
[Gobject docs](https://docs.gtk.org/gobject/floating-refs.html) mention the
following about floating references (i.e. transfer `floating`);
> Floating references are a C convenience API and should not be used in modern
GObject code. Language bindings in particular find the concept highly
problematic, as floating references are not identifiable through annotations,
...
Indeed, by the time `floating` makes it into the parsed annotation, it has
become `none`. And in case of a "factory" `some_widget_new()`, `floating`
behaves more like `full` as the caller must "take ownership" to avoid a leak. So
a "floating" `none` is quite different from a "real" `none` (e.g. "getter"
method). But no way to know from annotation data. So, in case of `none`, an
object wrapper always `ref_sink()`s. If it was floating, it has taken suitable
ownership. If it was really none, then it is now managing an extra refcount.
And in either case, it will release/decrement upon destruction. Essentially,
this follows the recommendation given in referenced docs. In practice, it
actually Just Works.
It gets really tricky when this is combined with e.g. lists. So what does `none`
mean in this case (in annotation)? In the worst case, the contained elements
might actually be floating, so one would have to go through the list and
`ref_sink` them all (un)conditionally? Suffice it to say, no such "automagic"
is handled/injected by any wrapper code. Fortunately, at this time there does
not seem to be such a "multiple factory" API. Even if there were, then in
practice the calling code is likely to loop over the list and access the
elements. The ensuing C++ wrappers (even if existing only briefly) would then
effectively `ref_sink()`, so again we are ok. And last but not least, by the
above quoted recommendation, there should be no such new tricky API coming
along. So, again, it Just Works. If needed, any such old or new API can and
should be handled by custom overrides.
**Boxed (by darkness).**
This refers to so-called "plain records" which are "C structs" with no
registered `GType` (referred to as "C boxed" types in `cppgir` code), e.g.
`GOptionEntry` or `GstMapInfo`. While their fields may be described in
annotations, there is no information regarding the "ownership" of any data
(which may even vary upon context). In particular, also no way to create/free.
This corresponds with their frequent stack-allocated use in C code in typically
"low-level" API which is usually not considered "binding friendly". Based on
the mild assumption that 0-initialized data makes a valid instance, they are
treated somewhat similar to (GType) boxed types and as such can be used in some
limited (function call) situations. Any improvement beyond that is likely to
remain in the purview of overrides.
### WORKAROUNDS
As C++ allows direct mixing/calls with C, there are usually some fallback
workarounds when confronted with one of the limitations. First of all,
note that a C++ wrapper typically has e.g. a `gobj_()` method that
provides the underlying C pointer/object. Conversely, `gi::wrap` can be
used to obtain a wrapper from a C pointer/object obtained by some means.
With that in mind, the following are some workarounds;
* function call;
using/given the above, the C function can then (simply) be called directly
* custom subclass virtual method;
use `--class-full` to generate a virtual method with plain C signature
* signal;
use `Object::connect_unchecked` (see also `gst.cpp` example)
* callback;
use `gi::callback_wrapper` (see also in same example location as above).
Or perhaps there is an API variant using closures which may be useful in
combination with some `Closure::from_*` helpers (see still same example).
## SEE ALSO
g-ir-scanner(1)

View File

@@ -0,0 +1,159 @@
#pragma once
#include <gi/gi.hpp>
#include <coroutine>
#include <future>
#ifdef CO_DEBUG
#include <iostream>
static auto &dout = std::cerr;
#else
#include <sstream>
static std::ostringstream dout;
#endif
template<typename T, typename SELF>
struct holder
{
void return_value(T &&v)
{
dout << "return value " << std::endl;
auto self = (SELF *)this;
self->set_value(std::move(v));
}
};
template<typename SELF>
struct holder<void, SELF>
{
void return_void()
{
auto self = (SELF *)this;
self->set_value();
}
};
template<typename RESULT>
class promise_type_t : public holder<RESULT, promise_type_t<RESULT>>
{
protected:
std::promise<RESULT> result_;
std::coroutine_handle<> waiter_;
public:
struct init
{
std::coroutine_handle<> handle;
std::future<RESULT> f;
};
~promise_type_t() { dout << "promise destruction" << std::endl; }
auto get_return_object(bool refresh = false)
{
dout << "return obj " << std::endl;
if (refresh)
result_ = decltype(result_)();
return init{std::coroutine_handle<promise_type_t>::from_promise(*this),
result_.get_future()};
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
bool resume()
{
auto w = waiter_;
if (w) {
// waiter takes care of itself again
waiter_ = nullptr;
w.resume();
}
return bool(w);
}
// use any dummy type to avoid reference to void below
using arg_type = typename std::conditional<std::is_same<RESULT, void>::value,
std::nullptr_t, RESULT>::type;
void set_value(arg_type &&v)
{
result_.set_value(std::move(v));
resume();
}
void set_value()
{
result_.set_value();
resume();
}
void set_waiter(std::coroutine_handle<> handle)
{
// a task/promise represent a coroutine function (frame)
// it should only be waited upon by one other task
// (rather than handed around and waited in multiple locations)
if (waiter_)
gi::detail::try_throw(std::logic_error("already waited upon"));
waiter_ = handle;
}
void unhandled_exception()
{
#if GI_CONFIG_EXCEPTIONS
result_.set_exception(std::current_exception());
// if no-one waiting, deliver to caller
// the latter likely is the original caller
// (to which we have not yet returned, so it can yet await)
// otherwise it might end up totally lost
if (!resume())
throw;
#endif
}
};
template<typename RESULT, typename P = promise_type_t<RESULT>>
class task
{
public:
using promise_type = P;
// only 1 actually active
std::coroutine_handle<promise_type> coro_;
std::unique_ptr<promise_type> p_;
// but always this
std::future<RESULT> result_;
public:
// NOTE if coroutine exits by co_return, then handle is not useful
// but the future should have a value
task(typename P::init i)
: coro_(decltype(coro_)::from_address(i.handle.address())),
result_(std::move(i.f))
{
dout << "init task" << std::endl;
}
task() : p_(new promise_type()) { result_ = p_->get_return_object().f; }
// move-only
task(task &&other) = default;
task &operator=(task &&other) = delete;
bool await_ready()
{
return result_.wait_for(std::chrono::seconds(0)) ==
std::future_status::ready;
}
promise_type &promise() const { return coro_ ? coro_.promise() : *p_; }
void await_suspend(std::coroutine_handle<> handle)
{
if (!coro_ && !p_)
gi::detail::try_throw(std::logic_error("no routine to wait on"));
promise().set_waiter(handle);
}
RESULT await_resume() { return result_.get(); }
};

View File

@@ -0,0 +1,74 @@
cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR)
project(cppgir_example VERSION 2.0.0)
# following example uses the Gio-2.0 gir
# will generate code for that
# locate cppgir
# NOTE recent cmake version have more options and automagic find_package integration
# (see https://cmake.org/cmake/help/latest/guide/using-dependencies/index.html
# or https://cmake.org/cmake/help/latest/module/FetchContent.html)
# but a slightly older approach is used here
find_package(cppgir ${CMAKE_PROJECT_VERSION} QUIET)
if (cppgir_FOUND)
# ok
message(STATUS "using system cppgir")
elseif (CMAKE_VERSION GREATER_EQUAL 3.22)
# older version fail processing the subdirectory below
message(STATUS "cppgir not found, fetching instead")
# of course, alternatively,
# use any other method to include cppgir (e.g. git submodule)
# which can also be done unconditionally (without trying find_package first)
include(FetchContent)
FetchContent_Declare(
cppgir
GIT_REPOSITORY https://gitlab.com/mnauw/cppgir.git
GIT_TAG master
GIT_SHALLOW TRUE
GIT_SUBMODULES_RECURSE TRUE
# disable auto-invoke of add_subdirectory() below
SOURCE_SUBDIR data
)
# no CMakeLists in specified subdir, so we handle it explicitly
FetchContent_MakeAvailable(cppgir)
set(CPPGIR_DIR "${cppgir_SOURCE_DIR}")
else ()
# obviously, actual mileage/location may vary (e.g. using git submodule)
set(CPPGIR_DIR "../..")
endif ()
if (CPPGIR_DIR)
# adjust options
set(BUILD_TESTING OFF)
set(BUILD_EXAMPLES OFF)
set(BUILD_EMBED_IGNORE ON)
add_subdirectory(${CPPGIR_DIR} cppgir EXCLUDE_FROM_ALL)
endif ()
set(EXAMPLE_NS "Gio-2.0")
# of course, can be anywhere, e.g. inside/outside the build directory
# set(GENERATED_DIR "/tmp/gi.example")
set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
add_custom_command(OUTPUT ${GENERATED_DIR}
COMMENT "Generating wrapper code for: ${EXAMPLE_NS}"
DEPENDS CppGir::cppgir
COMMAND CppGir::cppgir --output ${GENERATED_DIR} ${EXAMPLE_NS}
COMMAND cmake -E touch_nocreate ${GENERATED_DIR}
)
add_custom_target(generate DEPENDS ${GENERATED_DIR})
# need the code and libs and especially GIRs
include(FindPkgConfig)
pkg_check_modules(GOBJECT REQUIRED IMPORTED_TARGET gobject-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0 gio-unix-2.0)
# (actually) needs no generated code, but it needs basic libs
add_executable(ext-gobject src/ext-gobject.cpp)
target_link_libraries(ext-gobject PRIVATE CppGir::gi PkgConfig::GOBJECT)
# (really) needs generated code
add_executable(ext-gio src/ext-gio.cpp)
target_link_libraries(ext-gio PRIVATE CppGir::gi PkgConfig::GIO)
target_include_directories(ext-gio PRIVATE ${GENERATED_DIR})
add_dependencies(ext-gio generate)

View File

@@ -0,0 +1,6 @@
# cppgir_example
This example project shows how `cppgir` can be used with `CMake` or `meson`.
In either case, the required `cppgir` parts can either be found "in the system"
(as previously installed by some package or build), or built as part of the
project itself (by suitable form of inclusion).

View File

@@ -0,0 +1,49 @@
import os
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
from conan.tools.build import can_run
class CppGirExampleConan(ConanFile):
version = "2.0.0"
name = "cppgir-example"
description = "cppgir example"
license = "MIT Software License"
url = "https://gitlab.com/mnauw/cppgir.git"
exports_sources = "src/*", "CMakeLists.txt"
settings = "os", "compiler", "build_type", "arch"
build_policy = "missing"
author = "Mark Nauwelaerts"
def requirements(self):
req = self.tested_reference_str
if req is None:
req = "cppgir/" + self.version
self.requires(req)
# also requires GLib
# however, no GIRs in conan binary package
# which nowadays requires a bootstrap dance with gobject-introspection
# so rely on a distro package instead
# self.requires("glib/2.78.3")
def build_requirements(self):
self.tool_requires("cppgir/<host_version>",
options={'header_only': False})
def layout(self):
cmake_layout(self, build_folder="build.conan")
def generate(self):
tc = CMakeToolchain(self)
tc.generate()
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
def test(self):
if can_run(self):
cmd = os.path.join(self.cpp.build.bindir, "ext-gobject")
self.run(cmd, env="conanrun")

View File

@@ -0,0 +1,57 @@
project(
'cppgir_example',
['c', 'cpp'],
version: '2.0.0',
meson_version: '>= 0.61',
default_options: ['warning_level=2', 'cpp_std=c++17'],
)
# following example uses the Gio-2.0 gir
# will generate code for that
# locate cppgir, either in system or subproject
# if subproject, arrange to embed ignore data
# (since it will run un-installed, so data can not be found in installed location)
gi = dependency(
'cppgir',
required: true,
version: '>=2.0.0',
fallback: 'cppgir',
default_options: ['build_embed_ignore=true'],
)
cppgir = find_program('cppgir', required: true)
generated_src = custom_target(
'src',
# actually it generates all this (and more)
# output : ['gio.cpp', 'gio.hpp', 'glib.cpp', 'glib.hpp', 'gobject.cpp', 'gobject.hpp'],
# but the example below will inline all of the above, so no source files are used
# (though it is recommended for a real case, rather than a full inline)
output: ['gio.hpp'],
console: true,
# output-top creates extra files in top-level (as oppposed to subdir)
# since "output" paths above "must not contain a path segment" (says meson, sigh)
command: [cppgir, '--output-top', '--output', '@OUTDIR@', 'Gio-2.0'],
)
# need the code and libs and especially GIRs
gobject = dependency('gobject-2.0', required: true)
gio = dependency('gio-2.0', required: true)
# the Gio GIR data also covers this, so generated code references it
gio_unix = dependency('gio-unix-2.0', required: true)
deps = [gi, gobject, gio, gio_unix]
# (actually) needs no generated code, but it needs basic libs
ex_gobject = executable(
'ext-gobject',
['src/ext-gobject.cpp'],
dependencies: deps,
)
# (really) needs generated code
ex_gio = executable(
'ext-gio',
['src/ext-gio.cpp', generated_src],
dependencies: deps,
)

View File

@@ -0,0 +1 @@
#include "../../gio.cpp"

View File

@@ -0,0 +1 @@
#include "../../gobject.cpp"

View File

@@ -0,0 +1,5 @@
[wrap-git]
url = https://gitlab.com/mnauw/cppgir.git
revision = master
clone-recursive = true
depth = 1

View File

@@ -0,0 +1,254 @@
#define GI_INLINE 1
#include <gio/gio.hpp>
#include "co-async.hpp"
namespace GLib = gi::repository::GLib;
namespace GObject_ = gi::repository::GObject;
namespace Gio = gi::repository::Gio;
template<typename R>
class async_result_promise_type_t : public promise_type_t<R>
{
public:
Gio::Cancellable cancel_;
};
template<typename RESULT = Gio::AsyncResult>
class async_result : public task<RESULT, async_result_promise_type_t<RESULT>>
{
public:
using super_type = task<RESULT, async_result_promise_type_t<RESULT>>;
using super_type::super_type;
using super_type::await_suspend;
// NOTE; in general, if await_ready == false, then there is no result yet,
// so the handle's frame should not have co_return'ed yet
// so the handle/promise should still be valid
template<typename OR>
void await_suspend(
std::coroutine_handle<async_result_promise_type_t<OR>> handle)
{
// propagate cancellable
// the handle should be valid
// (as await_ready == false, otherwise no suspend should happen)
handle.promise().cancel_ = this->promise().cancel_;
// usual suspend
super_type::await_suspend(handle);
}
operator Gio::AsyncReadyCallback()
{
// only makes sense in default case
static_assert(std::is_same<RESULT, Gio::AsyncResult>::value, "");
if (this->p_) {
// setup for new gio call
this->result_ = this->p_->get_return_object(true).f;
// also arrange for new cancellable below
this->p_->cancel_ = nullptr;
return [this](GObject_::Object, Gio::AsyncResult result) {
this->promise().return_value(std::move(result));
};
} else {
// so this task is the result of a coroutine frame
// then it should be completed by the latter, not a Gio callback
g_warning("no callback to complete coroutine");
return nullptr;
}
}
Gio::Cancellable cancellable()
{
Gio::Cancellable ret;
if (this->p_) {
if (!this->p_->cancel_)
this->p_->cancel_ = Gio::Cancellable::new_();
ret = this->p_->cancel_;
} else if (!this->await_ready()) {
// no create here, only propagate
ret = this->promise().cancel_;
}
return ret;
}
static Gio::Cancellable timeout(
const std::chrono::milliseconds &to, Gio::Cancellable cancel)
{
if (to.count() > 0 && cancel) {
GLib::timeout_add_once(
to.count(), [cancel]() mutable { cancel.cancel(); });
}
return cancel;
}
};
async_result<void>
sleep_for(std::chrono::milliseconds to)
{
async_result<void> w;
GLib::timeout_add_once(to.count(), [&w] { w.promise().return_void(); });
co_await w;
co_return;
}
static task<void>
async_client(int port, int id, int &count)
{
async_result w;
auto dest = Gio::NetworkAddress::new_loopback(port);
std::string sid = "client ";
sid += std::to_string(id);
// connect a client
std::cout << sid << ": connect" << std::endl;
auto client = Gio::SocketClient::new_();
client.connect_async(dest, w);
auto conn = gi::expect(client.connect_finish(co_await w));
// say something
auto os = conn.get_output_stream();
std::cout << sid << ": send: " << sid << std::endl;
os.write_all_async(
(guint8 *)sid.data(), sid.size(), GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(co_await w, (gsize *)nullptr);
// now hear what the other side has to say
std::cout << sid << ": receive" << std::endl;
auto is = conn.get_input_stream();
while (1) {
guint8 data[1024];
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_, w);
auto size = gi::expect(is.read_finish(co_await w));
if (!size)
break;
std::string msg(data, data + size);
std::cout << sid << ": got data: " << msg << std::endl;
}
std::cout << sid << ": closing down" << std::endl;
--count;
}
static task<void>
async_handle_client(Gio::SocketConnection conn)
{
async_result w;
// say hello
auto os = conn.get_output_stream();
std::string msg = "hello ";
os.write_all_async(
(guint8 *)msg.data(), msg.size(), GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(co_await w, (gsize *)nullptr);
// now echo what the other side has to say
auto is = conn.get_input_stream();
while (1) {
guint8 data[1024];
// give up if timeout
GLib::Error error;
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_,
w.timeout(std::chrono::milliseconds(200), w.cancellable()), w);
auto size = gi::expect(is.read_finish(co_await w, &error));
if (error) {
if (error.matches(G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
break;
} else {
gi::detail::try_throw(std::move(error));
}
}
std::string msg(data, data + size);
std::cout << "server: got data: " << msg << std::endl;
os.write_all_async(data, size, GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(co_await w, (gsize *)nullptr);
}
std::cout << "server: closing down client" << std::endl;
}
static task<void>
async_server(int clients, int &port)
{
async_result w;
auto listener = Gio::SocketListener::new_();
port = gi::expect(listener.add_any_inet_port());
int count = 0;
while (count < clients) {
// accept clients
std::cout << "server: accepting" << std::endl;
listener.accept_async(w);
auto conn = gi::expect(
listener.accept_finish(co_await w, (GObject_::Object *)nullptr));
// spawn client handler
std::cout << "server: new connection" << std::endl;
// task will run itself to completion, no need to wait/watch it here
async_handle_client(conn);
++count;
}
// wait a bit more and shutdown, because we can
co_await sleep_for(std::chrono::milliseconds(1000));
}
void
async_demo(GLib::MainLoop loop, int clients)
{
// run server
// dispatch at once to obtain port
int port = 0;
auto server = async_server(clients, port);
// make clients
int count = 0;
for (int i = 0; i < clients; ++i) {
++count;
// client task runs to completion, no need to wait/watch
// NOTE this frame will stay alive, so count ref is valid
async_client(port, i, count);
}
// plain-and-simple; poll regularly and quit when all clients done
task<void> cd;
auto check = [&]() -> gboolean {
if (!count) {
cd.promise().return_void();
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
};
GLib::timeout_add(100, check);
auto wait = [&]() -> task<void> {
// wait clients
co_await cd;
// server should also have completed
co_await server;
std::cout << "server down" << std::endl;
loop.quit();
};
wait();
std::cout << "running loop" << std::endl;
loop.run();
std::cout << "ending loop" << std::endl;
}
int
main(int argc, char **argv)
{
auto loop = GLib::MainLoop::new_();
int clients = argc > 1 ? std::stoi(argv[1]) : 0;
std::cout << clients << " clients" << std::endl;
if (clients > 0)
async_demo(loop, clients);
}

View File

@@ -0,0 +1,351 @@
#define GI_INLINE 1
#include <gio/gio.hpp>
#include <iostream>
#if GI_CONFIG_EXCEPTIONS
#include <boost/fiber/all.hpp>
namespace GLib = gi::repository::GLib;
namespace GObject_ = gi::repository::GObject;
namespace Gio = gi::repository::Gio;
class context_scheduler : public boost::fibers::algo::round_robin
{
typedef context_scheduler self;
typedef boost::fibers::algo::round_robin super;
struct src : GSource
{
self *scheduler;
};
GSourceFuncs funcs{};
GLib::MainContext ctx_;
src *source_;
boost::fibers::condition_variable cond_;
boost::fibers::mutex mtx_;
using clock_type = std::chrono::steady_clock;
bool dispatching_ = false;
static gboolean src_dispatch(
GSource *source, GSourceFunc /*callback*/, gpointer /*user_data*/)
{
auto s = (src *)(source);
auto sched = s->scheduler;
// wait here to give (other) fibers a chance
sched->dispatching_ = true;
std::unique_lock<boost::fibers::mutex> lk(sched->mtx_);
sched->cond_.wait(lk);
sched->dispatching_ = false;
// all available work has been done while we were waiting above
// no need to dispatch again until new work
// which we accept as of now (due to mainloop activity)
return G_SOURCE_CONTINUE;
}
public:
context_scheduler(GLib::MainContext ctx) : ctx_(ctx)
{
// this is a bit too much for bindings, so handle the raw C way
funcs.dispatch = src_dispatch;
auto s = g_source_new(&funcs, sizeof(src));
source_ = (src *)(s);
source_->scheduler = this;
g_source_attach(s, ctx.gobj_());
}
~context_scheduler()
{
g_source_destroy(source_);
g_source_unref(source_);
}
void awakened(boost::fibers::context *t) noexcept override
{
// delegate first
super::awakened(t);
// arrange for dispatch of work
// discard awake of source dispatch
if (!dispatching_)
g_source_set_ready_time(source_, 0);
}
void suspend_until(
std::chrono::steady_clock::time_point const &abs_time) noexcept override
{
// release dispatch
// should only end up here while dispatching in source
// (rather than inadvertently trying to block main loop,
// which would then lead to busy loop)
if (dispatching_) {
// derive time of subsequent dispatch
if (clock_type::time_point::max() != abs_time) {
auto to = abs_time - std::chrono::steady_clock::now();
int ms =
std::chrono::duration_cast<std::chrono::milliseconds>(to).count();
ms = std::max(ms, 0);
g_source_set_ready_time(source_, g_get_monotonic_time() + ms);
} else {
g_source_set_ready_time(source_, -1);
}
// release source dispatching
cond_.notify_one();
} else {
// suspend is requested, so there is nothing to do for a while
// so in particular the main fiber is then also blocked (e.g. some sleep)
// such main loop blocking is also/still not allowed
// (if such is active)
g_assert(g_main_depth() == 0);
// no running loop (so also no dispatch)
// so delegate to the usual scheduling
// (which will really block the hard way, rather than poll)
super::suspend_until(abs_time);
}
}
// might be called from a different thread
void notify() noexcept override
{
// discard our own notify above to resume source dispatch
if (dispatching_)
return;
ctx_.wakeup();
}
};
class async_future
{
boost::fibers::promise<Gio::AsyncResult> p_;
boost::fibers::future<Gio::AsyncResult> f_;
Gio::Cancellable cancel_;
GLib::Source timeout_;
public:
~async_future()
{
if (timeout_)
timeout_.destroy();
}
operator Gio::AsyncReadyCallback()
{
// prepare a new promise
p_ = decltype(p_)();
f_ = p_.get_future();
cancel_ = nullptr;
return [&](GObject_::Object, Gio::AsyncResult result) {
p_.set_value(result);
};
}
Gio::AsyncResult get() { return f_.get(); }
Gio::Cancellable cancellable()
{
if (!cancel_)
cancel_ = Gio::Cancellable::new_();
return cancel_;
}
Gio::Cancellable timeout(const std::chrono::milliseconds &to)
{
auto cancel = cancellable();
if (to.count() > 0) {
timeout_ = GLib::timeout_source_new(to.count());
auto do_timeout = [cancel]() mutable {
cancel.cancel();
return GLib::SOURCE_REMOVE_;
};
timeout_.set_callback<GLib::SourceFunc>(do_timeout);
timeout_.attach(GLib::MainContext::get_thread_default());
}
return cancel_;
}
};
static void
async_client(int port, int id, int &count)
{
async_future w;
auto dest = Gio::NetworkAddress::new_loopback(port);
std::string sid = "client ";
sid += std::to_string(id);
// connect a client
std::cout << sid << ": connect" << std::endl;
auto client = Gio::SocketClient::new_();
client.connect_async(dest, w);
auto conn = gi::expect(client.connect_finish(w.get()));
// say something
auto os = conn.get_output_stream();
std::cout << sid << ": send: " << sid << std::endl;
os.write_all_async(
(guint8 *)sid.data(), sid.size(), GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(w.get(), (gsize *)nullptr);
// now hear what the other side has to say
std::cout << sid << ": receive" << std::endl;
auto is = conn.get_input_stream();
while (1) {
guint8 data[1024];
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_, w);
auto size = gi::expect(is.read_finish(w.get()));
if (!size)
break;
std::string msg(data, data + size);
std::cout << sid << ": got data: " << msg << std::endl;
}
std::cout << sid << ": closing down" << std::endl;
--count;
}
static void
async_handle_client(Gio::SocketConnection conn)
{
async_future w;
// say hello
auto os = conn.get_output_stream();
std::string msg = "hello ";
os.write_all_async(
(guint8 *)msg.data(), msg.size(), GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(w.get(), (gsize *)nullptr);
// now echo what the other side has to say
auto is = conn.get_input_stream();
while (1) {
guint8 data[1024];
// give up if timeout
GLib::Error error;
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_,
w.timeout(std::chrono::milliseconds(200)), w);
auto size = gi::expect(is.read_finish(w.get(), &error));
if (error) {
if (error.matches(G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
break;
} else {
throw error;
}
}
std::string msg(data, data + size);
std::cout << "server: got data: " << msg << std::endl;
os.write_all_async(data, size, GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(w.get(), (gsize *)nullptr);
}
std::cout << "server: closing down client" << std::endl;
}
static void
async_server(int clients, int &port)
{
async_future w;
auto listener = Gio::SocketListener::new_();
port = gi::expect(listener.add_any_inet_port());
int count = 0;
while (count < clients) {
// accept clients
std::cout << "server: accepting" << std::endl;
listener.accept_async(w);
auto conn = gi::expect(
listener.accept_finish(w.get(), (GObject_::Object *)nullptr));
// spawn client handler
std::cout << "server: new connection" << std::endl;
boost::fibers::fiber c(async_handle_client, conn);
c.detach();
++count;
}
// wait a bit and shutdown
// wait long enough to test the out-of-loop join below
boost::this_fiber::sleep_for(std::chrono::milliseconds(1000));
std::cout << "server: shutdown" << std::endl;
}
static void
async_demo(GLib::MainLoop loop, int clients)
{
// run server
// dispatch at once to obtain port
int port = 0;
boost::fibers::fiber server(
boost::fibers::launch::dispatch, async_server, clients, std::ref(port));
// make clients
int count = 0;
for (int i = 0; i < clients; ++i) {
++count;
auto c = boost::fibers::fiber(async_client, port, i, std::ref(count));
c.detach();
}
// plain-and-simple; poll regularly and quit when all clients done
auto check = [&]() {
if (!count)
loop.quit();
return G_SOURCE_CONTINUE;
};
GLib::timeout_add(100, check);
std::cout << "running loop" << std::endl;
loop.run();
std::cout << "ending loop" << std::endl;
server.join();
}
int
main(int argc, char **argv)
{
GLib::MainLoop loop = GLib::MainLoop::new_();
auto ctx = GLib::MainContext::default_();
boost::fibers::use_scheduling_algorithm<context_scheduler>(ctx);
{ // basic fiber demo
int count = 0;
auto work = [&](const std::string &msg) {
std::cout << msg << std::endl;
++count;
};
auto quit = [&](int limit, const std::chrono::milliseconds &d) {
while (count < limit)
boost::this_fiber::sleep_for(d);
loop.quit();
};
boost::fibers::fiber f1(work, "fiber 1");
boost::fibers::fiber f2(work, "fiber 2");
boost::fibers::fiber q(quit, 2, std::chrono::milliseconds(100));
loop.run();
f1.join();
f2.join();
q.join();
}
// now an optional async GIO demo
int clients = argc > 1 ? std::stoi(argv[1]) : 0;
std::cout << clients << " clients" << std::endl;
if (clients > 0)
async_demo(loop, clients);
}
#else
int
main(int argc, char **argv)
{
(void)argc;
(void)argv;
std::cerr << "boost::fibers needs exceptions";
}
#endif

View File

@@ -0,0 +1,63 @@
#define GI_INLINE 1
#include <gio/gio.hpp>
#include <iostream>
namespace GObject_ = gi::repository::GObject;
namespace GLib = gi::repository::GLib;
namespace Gio = gi::repository::Gio;
static GLib::MainLoop loop;
// many calls here support GError
// so typically will throw here instead
// (unless GError output is explicitly requested in call signature)
// NOTE abundancy of gi::expect not generally needed;
// only needed when using --dl and --expected
static void
on_reply(GObject_::Object ob, Gio::AsyncResult result)
{
// if not caught here, it will be caught before returning to plain C
#if GI_CONFIG_EXCEPTIONS
try {
#endif
auto connection = gi::object_cast<Gio::DBusConnection>(ob);
auto call_result = gi::expect(connection.call_finish(result));
// get single array child
auto names = gi::expect(call_result.get_child_value(0));
int count = gi::expect(names.n_children());
std::cout << count << " message bus names: " << std::endl;
for (int i = 0; i < count; ++i)
std::cout << gi::expect(
gi::expect(names.get_child_value(i)).get_string(nullptr))
<< std::endl;
#if GI_CONFIG_EXCEPTIONS
} catch (const GLib::Error &error) {
std::cerr << "error: '" << error.what() << "'." << std::endl;
}
#endif
// quit when idle
GLib::idle_add([]() {
loop.quit();
return GLib::SOURCE_REMOVE_;
});
}
int
main(int argc, char ** /*argv*/)
{
auto bustype = argc <= 1 ? Gio::BusType::SESSION_ : Gio::BusType::SYSTEM_;
auto connection = gi::expect(Gio::bus_get_sync(bustype));
connection.call("org.freedesktop.DBus", "/org/freedesktop/DBus",
"org.freedesktop.DBus", "ListNames", nullptr, nullptr,
Gio::DBusCallFlags::NONE_, -1, nullptr, on_reply);
loop = gi::expect(GLib::MainLoop::new_());
loop.run();
}

View File

@@ -0,0 +1,225 @@
#define GI_INLINE 1
#include <gio/gio.hpp>
#include <functional>
#include <iostream>
#include <memory>
#include <QCoreApplication>
#include <QFuture>
#include <QFutureWatcher>
#include <QObject>
namespace GLib = gi::repository::GLib;
namespace GObject_ = gi::repository::GObject;
namespace Gio = gi::repository::Gio;
template<typename T>
using ResultExtractor = std::function<void(
GObject_::Object, Gio::AsyncResult result, QFutureInterface<T> &fut)>;
template<typename T>
class qt_future
{
struct CallbackData
{
QFutureInterface<T> promise;
QFutureWatcher<T> watcher;
Gio::Cancellable cancel;
ResultExtractor<T> handler;
};
std::shared_ptr<CallbackData> data_;
public:
qt_future(ResultExtractor<T> h)
{
data_ = std::make_shared<CallbackData>();
data_->handler = h;
}
QFuture<T> future() { return data_->promise.future(); }
QFutureInterface<T> promise() { return data_->promise; }
operator Gio::AsyncReadyCallback()
{
auto d = data_;
return [d](GObject_::Object obj, Gio::AsyncResult result) {
assert(d->handler);
d->handler(obj, result, d->promise);
};
}
Gio::Cancellable cancellable()
{
if (!data_->cancel) {
auto cancel = data_->cancel = Gio::Cancellable::new_();
data_->watcher.setFuture(future());
auto h = [cancel]() mutable { cancel.cancel(); };
data_->watcher.connect(
&data_->watcher, &decltype(data_->watcher)::finished, std::move(h));
}
return data_->cancel;
}
// why not ... ??
operator Gio::Cancellable() { return cancellable(); }
};
#if GI_CONST_METHOD
#define CONST_METHOD const
#else
#define CONST_METHOD
#endif
template<typename Result, typename Object>
qt_future<Result>
make_future(Result (Object::*mf)(Gio::AsyncResult, GLib::Error *) CONST_METHOD)
{
ResultExtractor<Result> f;
f = [mf](GObject_::Object cbobj, Gio::AsyncResult result,
QFutureInterface<Result> &fut) mutable {
auto obj = gi::object_cast<Object>(cbobj);
GLib::Error error{};
auto res = (obj.*mf)(result, &error);
if (error.gobj_()) {
// FIXME use std::expected<Result> as a result type ??
// reportException might be an option, if enabled, but leads to throw
fut.reportFinished();
qWarning() << "error code " << error.code_();
qWarning() << "error message "
<< QString::fromUtf8(error.message_().c_str());
} else {
fut.reportFinished(&res);
}
};
return {f};
}
// ====
// some small future-centric API wrappers using above helper class
QFuture<Gio::FileInfo>
file_query_file_system_info(
Gio::File f, const std::string &attributes, gint io_priority)
{
auto fut = make_future(&Gio::File::query_filesystem_info_finish);
f.query_filesystem_info_async(attributes, io_priority, fut, fut);
return fut.future();
}
QFuture<bool>
file_copy(Gio::File src, Gio::File destination, Gio::FileCopyFlags flags,
gint io_priority)
{
auto fut = make_future(&Gio::File::copy_finish);
auto promise = fut.promise();
auto p = [promise](
goffset current_num_bytes, goffset total_num_bytes) mutable {
promise.setProgressRange(0, total_num_bytes);
promise.setProgressValue(current_num_bytes);
};
src.copy_async(destination, flags, io_priority, fut, p, fut);
return fut.future();
}
// ====
// save on typing elsewhere
// also makes it movable in a way
template<typename T>
std::shared_ptr<QFutureWatcher<T>>
make_watcher(QFuture<T> fut)
{
auto watcher = std::make_shared<QFutureWatcher<T>>();
watcher->setFuture(fut);
return watcher;
}
// type-erased owner/cleanup type
using Retainer = std::shared_ptr<std::nullptr_t>;
Retainer
do_query(QCoreApplication &app, const std::string &fpath)
{
auto file = Gio::File::new_for_commandline_arg(fpath);
auto fut = file_query_file_system_info(file, "*", GLib::PRIORITY_DEFAULT_);
auto watcher = make_watcher(fut);
auto h = [fut, &app]() {
Q_ASSERT(fut.isFinished());
std::cout << "query finished" << std::endl;
if (fut.resultCount()) {
auto finfo = fut.result();
for (auto attr : {Gio::FILE_ATTRIBUTE_FILESYSTEM_TYPE_,
Gio::FILE_ATTRIBUTE_FILESYSTEM_FREE_,
Gio::FILE_ATTRIBUTE_FILESYSTEM_SIZE_}) {
std::cout << attr << ": " << finfo.get_attribute_as_string(attr)
<< std::endl;
}
} else {
std::cout << "... but no results" << std::endl;
}
app.quit();
};
watcher->connect(
watcher.get(), &decltype(watcher)::element_type::finished, &app, h);
return {watcher, nullptr};
}
Retainer
do_copy(QCoreApplication &app, const std::string &src, const std::string &dest)
{
auto fsrc = Gio::File::new_for_commandline_arg(src);
auto fdest = Gio::File::new_for_commandline_arg(dest);
auto fut = file_copy(
fsrc, fdest, Gio::FileCopyFlags::ALL_METADATA_, GLib::PRIORITY_DEFAULT_);
auto watcher = make_watcher(fut);
auto h = [fut, &app]() {
Q_ASSERT(fut.isFinished());
std::cout << "copy finished" << std::endl;
if (fut.resultCount()) {
auto ok = fut.result();
if (ok) {
std::cout << "copy ok" << std::endl;
} else {
std::cout << "copy failed" << std::endl;
}
} else {
std::cout << "... but no results" << std::endl;
}
app.quit();
};
watcher->connect(
watcher.get(), &decltype(watcher)::element_type::finished, &app, h);
// also monitor progress
auto progress = [fut](int) {
auto max = fut.progressMaximum();
auto min = fut.progressMinimum();
auto current = fut.progressValue();
std::cout << "copy progress " << current << " (" << min << " -> " << max
<< ")" << std::endl;
};
watcher->connect(watcher.get(),
&decltype(watcher)::element_type::progressValueChanged, &app, progress);
return {watcher, nullptr};
}
int
main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
if (argc < 2)
return -1;
Retainer r;
if (argc == 2) {
r = do_query(app, argv[1]);
} else if (argc == 3) {
r = do_copy(app, argv[1], argv[2]);
}
return app.exec();
}

View File

@@ -0,0 +1,98 @@
#define GI_INLINE 1
#include <gio/gio.hpp>
#include <iostream>
namespace GLib = gi::repository::GLib;
namespace Gio = gi::repository::Gio;
const std::string localhost("127.0.0.1");
static GLib::MainLoop loop;
// many calls here support GError
// so typically will throw here instead
// (unless GError output is explicitly requested in call signature)
// NOTE abundancy of gi::expect not generally needed;
// only needed when using --dl and --expected
static bool
receive(Gio::Socket s, GLib::IOCondition /*cond*/)
{
guint8 buffer[1024] = {
0,
};
Gio::SocketAddress a;
int count = gi::expect(s.receive_from(&a, buffer, sizeof(buffer)));
if (count > 0) {
// let's see where it came from
std::string origin("someone");
auto ia = gi::object_cast<Gio::InetSocketAddress>(a);
if (ia) {
origin = gi::expect(gi::expect(ia.get_address()).to_string());
origin += ":";
origin += std::to_string(gi::expect(ia.get_port()));
}
std::cout << origin << " said " << (char *)buffer << std::endl;
// quit when idle
GLib::idle_add([]() {
loop.quit();
return GLib::SOURCE_REMOVE_;
});
}
return true;
}
Gio::Socket
open(bool listen)
{
auto socket = gi::expect(Gio::Socket::new_(Gio::SocketFamily::IPV4_,
Gio::SocketType::DATAGRAM_, Gio::SocketProtocol::DEFAULT_));
auto address =
gi::expect(Gio::InetSocketAddress::new_from_string(localhost, 0));
socket.bind(address, false);
socket.set_blocking(false);
if (listen) {
// runtime introspection has a hard time here,
// but with a bit of extra information, we can keep going
#if defined(GI_CALL_ARGS) && CALL_ARGS <= 1
// so we should have this signature for a function with 1 non-required
GLib::Source source =
gi::expect(socket.create_source({.condition = GLib::IOCondition::IN_}));
#else
GLib::Source source =
gi::expect(socket.create_source(GLib::IOCondition::IN_, nullptr));
#endif
source.set_callback<Gio::SocketSourceFunc>(receive);
source.attach();
}
return socket;
}
static void
die(const std::string &why)
{
std::cerr << why << std::endl;
exit(2);
}
int
main(int argc, char **argv)
{
if (argc < 2)
die("missing argument");
std::string msg = argv[1];
std::cout << "will send message " << msg << std::endl;
auto recv = open(true);
auto local = gi::expect(recv.get_local_address());
auto send = open(false);
send.send_to(local, (guint8 *)msg.data(), msg.size(), nullptr);
loop = gi::expect(GLib::MainLoop::new_());
loop.run();
}

View File

@@ -0,0 +1,178 @@
// no code generation is needed
// all required functionality is required by the basic code
#include <gi/gi.hpp>
#include <iostream>
namespace GObject_ = gi::repository::GObject;
static const int DEFAULT_AGE = 10;
static const char *PERSON_TYPE = "GIPerson";
// class must be a ObjectImpl to support properties and signals
class Person : public GObject_::impl::ObjectImpl
{
using self_type = Person;
private:
// the implementation/definition part of the properties
// the setup parameters shadow the corresponding g_param_spec_xxx
// so in practice define the property (name, nick, description)
// along with min, max, default and so (where applicable)
// (could also be in a constructor initializer list,
// but this way it applies to any constructor)
// this provides interface to set/get the actual value
gi::property<int> prop_age_{this, "age", "age", "age", 0, 100, DEFAULT_AGE};
gi::property<std::string> prop_firstname_{
this, "firstname", "firstname", "firstname", ""};
gi::property<std::string> prop_lastname_{
this, "lastname", "lastname", "lastname", ""};
public:
// likewise for signal
// public because there is no extra interface for owning class
// (both owner and outside can connect and/or emit)
// btw, using Person in this signature would not be the way to go,
// should stick to a plain wrapped type
gi::signal<void(GObject_::Object, int)> signal_trigger{this, "trigger"};
// std::string also works here,
// but that would cost an extra allocation during invocation
gi::signal<void(GObject_::Object, gi::cstring_v)> signal_example{
this, "example"};
public:
// old-style C++ first
Person() : ObjectImpl(this) {}
// new-style C-first;
// must use proper signature for constructor and superclass
// this also registers a "public" GType, whose name must be specified
Person(const InitData &id) : ObjectImpl(this, id, PERSON_TYPE) {}
// usually the above constructor is also used to during GType registration,
// as such "dummy instantiation" auto-magically collects property info, etc)
// however, it does involve an "incomplete" instantiation (with no .gobj_())
// if such is considered too inelegant or too costly, then alternatively
// a ::get_type_() can be defined that is then used to register type instead
// however, as the example below demonstrates, this may be much less ergonomic
// so it is likely only useful if really so desired or in advanced cases)
// (e.g. subclass-ing a subclass)
static GType get_type_disabled() // not really used due to extra suffix
{
// if this is used, the member initializers only need to mention name
// no longer the full spec(ification)
// (as the name then only serves to link the propertyspec to actual storage)
return register_type_<Person>(PERSON_TYPE, 0, {},
{{&self_type::prop_age_, "age", "age", "age", 0, 100, DEFAULT_AGE},
{&self_type::prop_firstname_, "firstname", "firstname", "firstname",
""},
{&self_type::prop_lastname_, "lastname", "lastname", "lastname",
""}},
{{&self_type::signal_trigger, "trigger"},
{&self_type::signal_example, "example"}});
}
// the public counterpart providing the same interface
// as with any wrapped object's predefined properties
gi::property_proxy<int> prop_age() { return prop_age_.get_proxy(); }
gi::property_proxy<std::string> prop_firstname()
{
return prop_firstname_.get_proxy();
}
gi::property_proxy<std::string> prop_lastname()
{
return prop_lastname_.get_proxy();
}
void action(int id)
{
std::cout << "Changing the properties of 'p'" << std::endl;
prop_firstname_ = "John";
prop_lastname_ = "Doe";
prop_age_ = 43;
std::cout << "Done changing the properties of 'p'" << std::endl;
// we were triggered after all
signal_trigger.emit(id);
}
};
void
on_firstname_changed(GObject_::Object, GObject_::ParamSpec)
{
std::cout << "- firstname changed!" << std::endl;
}
void
on_lastname_changed(GObject_::Object, GObject_::ParamSpec)
{
std::cout << "- lastname changed!" << std::endl;
}
void
on_age_changed(GObject_::Object, GObject_::ParamSpec)
{
std::cout << "- age changed!" << std::endl;
}
int
main(int /*argc*/, char ** /*argv*/)
{
Person p;
// should have default age
assert(p.prop_age().get() == DEFAULT_AGE);
// register some handlers that will be called when the values of the
// specified parameters are changed
p.prop_firstname().signal_notify().connect(&on_firstname_changed);
p.prop_lastname().signal_notify().connect(on_lastname_changed);
p.prop_age().signal_notify().connect(&on_age_changed);
// now change the properties and see that the handlers get called
p.action(0);
// (derived) object can be constructed on stack for simple cases
// but in other (real) cases it is recommended that it is heap based.
// so as not to have a naked ptr, it can be managed by a (special) shared
// ptr that uses the GObject refcount for (shared) ownership tracking
auto dp = gi::make_ref<Person, gi::construct_cpp_t>();
// however, the GObject world has no knowledge of the subclass
// (each instance is 1-to-1 with Person instance though).
// so when we get something from that world, we can (sort-of dynamic)
// cast to the subclass
auto l = [](GObject_::Object ob, int id) {
std::cout << " - triggered id " << id << std::endl;
// obtain Person
auto lp = gi::ref_ptr_cast<Person>(ob);
if (lp)
std::cout << " - it was a person!" << std::endl;
// it really should be ...
assert(lp);
};
dp->signal_trigger.connect(l);
dp->action(1);
// all of the above constructed Person in C++ centric style
// that is, as part of the constructor (super class) execution,
// a (derived) GObject is created and associated/extended with C++ side Person
// alternatively, another derived GObject type can be registered,
// which will trigger construction of a Person (as part of g_object_new()),
// and then again associate those
// the following essentially runs g_object_new()
static int CUSTOM_AGE = 5;
auto dpc = gi::make_ref<Person, gi::construct_c_t>("age", CUSTOM_AGE);
// this will have a different GType
assert(dpc->gobj_type_() != dp->gobj_type_());
// as registered using specified name
assert(g_type_from_name(PERSON_TYPE) == dpc->gobj_type_());
// and the right age as specified in (gobject style) construct params
assert(dpc->prop_age().get() == CUSTOM_AGE);
// if the class supports C-style, it is selected automatically
auto dpa = gi::make_ref<Person>();
// should have ended up with C-style GType
assert(dpa->gobj_type_() == dpc->gobj_type_());
return 0;
}

View File

@@ -0,0 +1,419 @@
#include <functional>
#include <iostream>
#include <memory>
#include <ostream>
#include <sstream>
#include <vector>
#include "assert.h"
#ifndef USE_GI_MODULE
// no inline as the implementation is provided by other TUs
// #define GI_INLINE 1
#include <gst/gst.hpp>
#else
// optional, but recommended if gi macros are used
#include <gi/gi_inc.hpp>
// we also use some gst macros and api
#include <gst/gst.h>
// import used modules
import gi.repo.gobject;
import gi.repo.gst;
#endif
namespace GLib = gi::repository::GLib;
namespace Gst = gi::repository::Gst;
namespace GObject_ = gi::repository::GObject;
void
say(const std::string &msg)
{
std::cout << msg << std::endl;
}
void
die(const std::string &why)
{
std::cerr << why << std::endl;
exit(2);
}
class Player
{
typedef Player self;
GLib::MainLoop loop_;
std::string url_;
Gst::Element playbin_;
bool live_ = false;
Gst::Element vfilter_;
Gst::Element afilter_;
std::map<std::string, Gst::Element> filter_;
GLib::SourceScopedConnection monitor_;
GObject_::SignalConnection busconn_;
public:
Player(GLib::MainLoop loop, const std::string &url) : loop_(loop), url_(url)
{
playbin_ = Gst::ElementFactory::make("playbin");
assert(playbin_);
// get a property
// this one is known at introspection time
// (so type is known and suitable checked)
auto name = playbin_.property_name().get();
std::cout << "created playbin " << name << std::endl;
}
void start()
{
// set some property
playbin_.set_property("uri", url_);
// ... or several ones
playbin_.set_properties("volume", 0.5, "mute", false);
// ... non-primitive also possible
vfilter_ = Gst::ElementFactory::make("identity", "vfilter");
afilter_ = Gst::ElementFactory::make("identity", "afilter");
playbin_.set_properties("video-filter", vfilter_, "audio-filter", afilter_);
// connect some; find out what the source element is
// signal not known to introspection, so have to provide signature here
playbin_.connect<void(Gst::Element, Gst::Element)>(
"source-setup", gi::mem_fun(&self::on_source_setup, this));
// for a known signal, it is a bit easier to connect
auto bus = playbin_.get_bus();
bus.add_signal_watch();
// signal connect; using introspected (hence checked) signal definition
// could do without the slot step here and directly pass the lambda,
// but wrapping it this way allows combining this with the returned
// (plain) id into a scoped connection guard
auto slot = bus.signal_message().slot(gi::mem_fun(&self::on_message, this));
busconn_ =
gi::make_connection(bus.signal_message().connect(slot), slot, bus);
// again, if the guard is not desired; the following simply suffices
if (false)
bus.signal_message().connect(gi::mem_fun(&self::on_message, this));
// if the signal signature is somehow not supported
// then the following is a fallback manual method
// which still allows to use lambda (and such)
// and also provides some ownership management
if (false) {
auto h = [](GstBus *, GstMessage *) {
// dummy no-op
};
bus.connect_unchecked<void(GstBus *, GstMessage *)>("message", h);
}
// likewise, if a callback is to be used whose arguments are not supported
// or in a function that is not supported, then the following is a fallback
// which still allows to use lambda (and such)
// and also provides some ownership management
if (false) {
auto h = []() { return false; };
auto cb = new gi::callback_wrapper<gboolean(), false>(h);
// now the above pointer is managed as user data
// and will be suitable destroyed when needed
g_idle_add_full(0, &cb->wrapper, cb, &cb->destroy);
// in case of a typical single-use async callback (with no GDestroyNotify)
// use true as AUTODESTROY template parameter
// (then it will auto-clean up after invoking callback)
}
// alternatively, maybe there is some API that accepts a Closure
// the following are some convenient ways to get a closure
if (false) {
GLib::LogFunc h = [](gi::cstring_v log_domain,
GLib::LogLevelFlags log_level,
gi::cstring_v message) {
(void)log_domain;
(void)log_level;
(void)message;
};
GObject_::Closure::from_callback(h);
}
// likewise, but with a bit more manual (C++) signature specification
if (false) {
auto h = [](GObject_::Object, int) {};
GObject_::Closure::from_functor<void(GObject_::Object, int)>(h);
}
say("Setting pipeline to PAUSED ...");
auto ret = playbin_.set_state(Gst::State::PAUSED_);
if (ret == Gst::StateChangeReturn::FAILURE_) {
die("Pipeline does not want to pause");
} else if (ret == Gst::StateChangeReturn::NO_PREROLL_) {
say("Pipeline is live and does not need PREROLL ...");
live_ = true;
} else if (ret == Gst::StateChangeReturn::ASYNC_) {
say("Pipeline is PREROLLING ...");
}
// inspect after a while
GLib::timeout_add_seconds(2, [this]() {
inspect();
return GLib::SOURCE_REMOVE_;
});
// some regular progress reporting ...
GLib::SourceFunc func = [this]() {
progress();
return GLib::SOURCE_CONTINUE_;
};
// the introspected function returns a plain id,
// which can be used in the usual way to disconnect
// e.g. at destructor time of owning object
// alternatively, a helper scoped object can take care of that
monitor_ = gi::make_connection(GLib::timeout_add_seconds(1, func), func);
// other such make_connection variations exist;
// e.g. for a signal connection, a probe callback
// (and can easily be custom added)
// add a pad probe; use a casual lambda
// but not too casual, mind (dangling) references/pointers though
filter_["video"] = vfilter_;
filter_["audio"] = afilter_;
for (auto &&p : filter_) {
if (p.second) {
Gst::Pad pad = p.second.get_static_pad("sink");
auto name = p.first;
auto handler = [name](Gst::Pad p, Gst::PadProbeInfo_Ref info) {
auto s = p.get_path_string();
s += "received " + name + " buffer";
auto buffer = info.get_buffer();
if (buffer) {
s += " of size ";
s += std::to_string(buffer.get_size());
}
return Gst::PadProbeReturn::REMOVE_;
};
pad.add_probe(Gst::PadProbeType::BUFFER_, handler);
}
}
// shamelessly demo some helpers that aid in caps/value handling
auto caps = Gst::Caps::new_empty_simple("video/x-raw");
caps.set_value("width", GObject_::Value(Gst::IntRange(240, 320)));
caps.set_value("pixel-aspect-ratio", GObject_::Value(Gst::Fraction(4, 3)));
caps.set_value(
"framerate", GObject_::Value(Gst::FractionRange({25, 1}, 30)));
// retrieving pretty much the same way
auto s = caps.get_structure(0);
auto par = s.get_value("pixel-aspect-ratio").get_value<Gst::Fraction>();
// helpers also stream to string properly
std::ostringstream oss;
oss << par;
oss << (Gst::FlagSet(1, 1) == Gst::FlagSet(2, 1));
// silly test code to exercise some operators
// Rank override allows for succinct numeric conversion
if (+Gst::Rank::PRIMARY_ + 0) {
// flags support various typical operations
(void)(Gst::PadProbeType::BUFFER_ | Gst::PadProbeType::BUFFER_LIST_);
}
}
void stop()
{
if (playbin_)
playbin_.set_state(Gst::State::NULL_);
loop_.quit();
}
void on_message(Gst::Bus /*bus*/, Gst::Message_Ref msg)
{
auto &&src = msg.src_();
switch (msg.type_()) {
case Gst::MessageType::EOS_:
say("Got EOS from " + src.get_path_string());
stop();
break;
case Gst::MessageType::ERROR_: {
GLib::Error err;
gi::cstring debug;
msg.parse_error(&err, &debug);
say("Got error from " + src.get_path_string());
if (debug.size())
say("debug info:\n" + debug);
break;
}
case Gst::MessageType::STATE_CHANGED_:
/* only handle top-level case */
if (src != playbin_)
break;
Gst::State old, new_, pending;
msg.parse_state_changed(&old, &new_, &pending);
if (new_ == Gst::State::PAUSED_ && old == Gst::State::READY_)
playbin_.set_state(Gst::State::PLAYING_);
break;
default:
// never mind
break;
}
}
void on_source_setup(Gst::Element /*pb*/, Gst::Element src)
{
say("source is " + src.get_path_string());
}
void inspect()
{
std::ostringstream oss;
// this should work
auto bin = gi::object_cast<Gst::Bin>(playbin_);
assert(bin);
// a dynamically loaded element may implement a number of interfaces
// which is not known at compile-time
// the cast above can also cast to interface, which can be obtained as
// follows if known at introspection/compile time
auto cp = bin.interface_(gi::interface_tag<Gst::ChildProxy>());
oss << "player bin has " << cp.get_children_count() << " children"
<< std::endl;
// get some properties (dynamically, i.e. not known at introspection
// time) type will have to match (i.e. transformable) at runtime
auto n_v = playbin_.get_property<int>("n-video");
auto n_a = playbin_.get_property<int>("n-audio");
// minimal stuff
oss << "sample streams: video=" << n_v << ", audio=" << n_a << std::endl;
// show some tag info
for (auto &&p :
std::map<std::string, int>{{"video", n_v}, {"audio", n_a}}) {
for (int i = 0; i < p.second; ++i) {
// the argument's type should match the signal definition
// (cast if needed to make it so)
auto action = std::string("get-");
action += p.first + "-tags";
auto taglist = playbin_.emit<Gst::TagList>(action, i);
if (!taglist)
continue;
auto ntags = taglist.n_tags();
oss << p.first << " stream " << i << std::endl;
for (int j = 0; j < ntags; ++j) {
auto tname = taglist.nth_tag_name(j);
auto value = taglist.get_value_index(tname, 0);
#if GI_CONFIG_EXCEPTIONS
try {
#endif
auto sval = value.transform_value<std::string>();
oss << " " << tname << ": " << sval << std::endl;
#if GI_CONFIG_EXCEPTIONS
} catch (...) {
// could be object or otherwise, never mind
}
#endif
}
}
}
// should also have caps here by now
// there are other ways to obtain this, but let's go this way here
for (auto &&p : filter_) {
if (p.second) {
Gst::Pad pad = p.second.get_static_pad("sink");
auto caps = pad.get_current_caps();
oss << p.first << " caps: " << caps.to_string() << std::endl;
}
}
say(oss.str());
say("Playbin elements:");
// Gst::Iterator could be used with native interface
// but a helper wrapper has been provided that supports ease-of-use as
// in ...
for (auto &&el : Gst::IteratorAdapter<Gst::Element>(bin.iterate_recurse()))
say(el.get_path_string());
say("");
}
static std::string time_to_str(Gst::ClockTime time)
{
// could be done otherwise
// but we have native C access at hand, so let's use that
auto s = g_strdup_printf("%" GST_TIME_FORMAT, GST_TIME_ARGS(time));
std::string ret(s);
g_free(s);
return ret;
}
void progress()
{
bool ok = true;
gint64 duration = -1, position = -1;
ok &= playbin_.query_duration(Gst::Format::TIME_, &duration);
ok &= playbin_.query_position(Gst::Format::TIME_, &position);
std::ostringstream oss;
if (ok) {
oss << "Duration: " << time_to_str(duration)
<< ", Position: " << time_to_str(position);
} else {
oss << "No progress info available";
}
say(oss.str());
}
};
#ifdef GI_CLASS_IMPL
// not used in the above, but serves as a subclass example
class ChattyBin : public Gst::impl::BinImpl
{
public:
#if 0
// this part is only needed if there is some conflict
// (among members of class and/or interfaces)
// otherwise it should be auto-detected
struct DefinitionData
{
GI_DEFINES_MEMBER(BinClassDef, add_element, true)
};
#endif
ChattyBin() : Gst::impl::BinImpl(this) {}
bool add_element_(Gst::Element element) noexcept override
{
say("adding element " + element.name_() + '\n');
return Gst::impl::BinImpl::add_element_(element);
}
};
#endif
int
main(int argc, char **argv)
{
if (argc < 2)
die("missing argument");
// C signature fits C main best anyway
gst_init(&argc, &argv);
std::string url = argv[1];
// make it URL if not so
if (!Gst::Uri::is_valid(url)) {
#if GI_CONFIG_EXCEPTIONS
try {
#endif
url = gi::expect(Gst::filename_to_uri(url));
#if GI_CONFIG_EXCEPTIONS
} catch (const GLib::Error &ex) {
die(ex.what());
}
#endif
}
say("Playing " + url);
// simply local var will do here
auto loop = GLib::MainLoop::new_();
Player player(loop, url);
// schedule start
GLib::idle_add([&] {
player.start();
return GLib::SOURCE_REMOVE_;
});
// ... and auto end after a while
GLib::timeout_add_seconds(10, [&] {
player.stop();
return GLib::SOURCE_REMOVE_;
});
loop.run();
}

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkListStore" id="liststore1">
<columns>
<column type="gchararray"/>
<column type="gchararray"/>
<column type="gint"/>
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">John</col>
<col id="1" translatable="yes">Doe</col>
<col id="2">25</col>
<col id="3" translatable="yes">This is the John Doe row</col>
</row>
<row>
<col id="0" translatable="yes">Mary</col>
<col id="1" translatable="yes">Unknown</col>
<col id="2">50</col>
<col id="3" translatable="yes">This is the Mary Unknown row</col>
</row>
</data>
</object>
<object class="GtkWindow" id="window1">
<property name="default-height">250</property>
<property name="default-width">440</property>
<property name="title" translatable="yes">Gtk::Builder demo</property>
<child>
<object class="GtkBox" id="vbox1">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="toolbar1">
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Help</property>
<property name="tooltip-text" translatable="yes">Help</property>
<property name="action-name">win.help</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkTreeView" id="treeview1">
<property name="model">liststore1</property>
<property name="tooltip-column">3</property>
<child>
<object class="GtkTreeViewColumn" id="column1">
<property name="title">Name</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="column2">
<property name="title">Surname</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="column3">
<property name="title">Age</property>
<child>
<object class="GtkCellRendererText" id="renderer3"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<!-- INSERT -->
<child>
<object class="GtkStatusbar" id="statusbar1"/>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -0,0 +1,2 @@
#define GI_INCLUDE_IMPL 1
#include <gtk/gtk.hpp>

View File

@@ -0,0 +1,429 @@
#ifndef USE_GI_MODULE
// no inline as the implementation is in another TU
// #define GI_INLINE 1
#include <gtk/gtk.hpp>
#else
// optional, but recommended if gi macros are used
#include <gi/gi_inc.hpp>
// we use some gtk macros
#include <gtk/gtk.h>
// import recursive module
import gi.repo.gtk.rec;
#endif
// adapt to API as needed
#if GTK_CHECK_VERSION(4, 0, 0)
#define GTK4 1
#endif
#include <fstream>
#include <iostream>
#include <tuple>
#include <vector>
namespace GLib = gi::repository::GLib;
namespace GObject_ = gi::repository::GObject;
namespace Gtk = gi::repository::Gtk;
static GLib::MainLoop loop;
#ifdef GI_CLASS_IMPL
// based on python-gtk3 example
// https://python-gtk-3-tutorial.readthedocs.io/en/latest/treeview.html
// list of tuples for each software,
// containing the software name, initial release, and main programming languages
const std::vector<std::tuple<std::string, int, std::string>> software_list{
std::make_tuple("Firefox", 2002, "C++"),
std::make_tuple("Eclipse", 2004, "Java"),
std::make_tuple("Pitivi", 2004, "Python"),
std::make_tuple("Netbeans", 1996, "Java"),
std::make_tuple("Chrome", 2008, "C++"),
std::make_tuple("Filezilla", 2001, "C++"),
std::make_tuple("Bazaar", 2005, "Python"),
std::make_tuple("Git", 2005, "C"),
std::make_tuple("Linux Kernel", 1991, "C"),
std::make_tuple("GCC", 1987, "C"),
std::make_tuple("Frostwire", 2004, "Java")};
class TreeViewFilterWindow : public Gtk::impl::WindowImpl
{
typedef TreeViewFilterWindow self_type;
Gtk::ListStore store_;
Gtk::TreeModelFilter language_filter_;
std::string current_filter_language_;
public:
TreeViewFilterWindow() : Gtk::impl::WindowImpl(this)
{
Gtk::Window &self = *(this);
self.set_title("TreeView filter demo");
#ifdef GTK4
#else
self.set_border_width(10);
#endif
// set up the grid in which elements are positioned
auto grid = Gtk::Grid::new_();
grid.set_column_homogeneous(true);
grid.set_row_homogeneous(true);
#ifdef GTK4
self.set_child(grid);
#if 0
// this could work, but the annotation for .load_from_data
// is too unstable across versions
{ // migrate call above to CSS style
const char *css = "grid { margin: 10px; }";
auto provider = Gtk::CssProvider::new_();
provider.load_from_data((guint8 *)css, -1);
grid.get_style_context().add_provider(
provider, Gtk::STYLE_PROVIDER_PRIORITY_USER_);
}
#endif
#else
self.add(grid);
#endif
// create ListStore model
store_ = Gtk::ListStore::new_type_<std::string, int, std::string>();
for (auto &e : software_list) {
auto it = store_.append();
GObject_::Value cols[] = {std::get<0>(e), std::get<1>(e), std::get<2>(e)};
for (unsigned i = 0; i < G_N_ELEMENTS(cols); ++i) {
store_.set_value(it, i, cols[i]);
}
}
// create the filter, feeding it with the liststore model
auto treemodel = store_.interface_(gi::interface_tag<Gtk::TreeModel>());
language_filter_ =
gi::object_cast<Gtk::TreeModelFilter>(treemodel.filter_new(nullptr));
// set the filter function
language_filter_.set_visible_func(
gi::mem_fun(&self_type::language_filter_func, this));
// create the treeview, make it use the filter as a model, and add
// columns
auto treeview = Gtk::TreeView::new_with_model(language_filter_);
int i = 0;
for (auto &e : {"Software", "Release Year", "Programming Language"}) {
auto renderer = Gtk::CellRendererText::new_();
auto column = Gtk::TreeViewColumn::new_(e, renderer, {{"text", i}});
treeview.append_column(column);
++i;
}
// create buttons to filter by programming language, and set up their
// events
std::vector<Gtk::Widget> buttons;
for (auto &prog_language : {"Java", "C", "C++", "Python", "None"}) {
auto button = Gtk::Button::new_with_label(prog_language);
buttons.push_back(button);
button.signal_clicked().connect(
gi::mem_fun(&self_type::on_selection_button_clicked, this));
}
// set up the layout;
// put the treeview in a scrollwindow, and the buttons in a row
auto scrollable_treelist = Gtk::ScrolledWindow::new_();
scrollable_treelist.set_vexpand(true);
grid.attach(scrollable_treelist, 0, 0, 8, 10);
grid.attach_next_to(
buttons[0], scrollable_treelist, Gtk::PositionType::BOTTOM_, 1, 1);
auto it = buttons.begin() + 1;
while (it != buttons.end()) {
grid.attach_next_to(*it, *(it - 1), Gtk::PositionType::RIGHT_, 1, 1);
++it;
}
#ifdef GTK4
scrollable_treelist.set_child(treeview);
self.show();
#else
scrollable_treelist.add(treeview);
self.show_all();
#endif
}
bool language_filter_func(Gtk::TreeModel filter, Gtk::TreeIter_Ref it) const
{
if (current_filter_language_.empty() || current_filter_language_ == "None")
return true;
return current_filter_language_ ==
filter.get_value(it, 2).get_value<std::string>();
}
void on_selection_button_clicked(Gtk::Button button)
{
// set the current language filter to the button's label
current_filter_language_ = button.get_label();
std::cout << current_filter_language_ << " language selected!" << std::endl;
// update the filter, which updates in turn the view
language_filter_.refilter();
}
};
// other part based on gtkmm builder example
namespace Gio = gi::repository::Gio;
auto templ = R"|(
<interface>
<template class="FooWidget" parent="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">4</property>
<child>
<object class="GtkButton" id="hello_button">
<property name="label">Hello World</property>
<signal name="clicked" handler="hello_button_clicked"/>
<signal name="clicked" handler="hello_button_clicked_object" object="label" swapped="no"/>
<signal name="clicked" handler="hello_button_clicked_object_swapped" object="label" swapped="yes"/>
</object>
</child>
<child>
<object class="GtkButton" id="goodbye_button">
<property name="label">Goodbye World</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label">
<property name="label">Greetings World</property>
</object>
</child>
</template>
</interface>
)|";
auto templ_child = R"|(
<child>
<object class="FooWidget" id="foowidget1"/>
</child>
)|";
using WidgetTemplateHelper = Gtk::impl::WidgetTemplateHelper;
class FooWidget : public Gtk::impl::BoxImpl, public WidgetTemplateHelper
{
using self_type = FooWidget;
public:
static void custom_class_init(GtkBoxClass *klass, gpointer)
{
// this is similar to the C case
// plain C functions are used, as klass struct has no direct equivalent
auto wklass = GTK_WIDGET_CLASS(klass);
auto bytes = GLib::Bytes::new_static((const guint8 *)templ, strlen(templ));
gtk_widget_class_set_template(wklass, bytes.gobj_());
gtk_widget_class_bind_template_child_full(wklass, "hello_button", TRUE, 0);
// chain up
WidgetTemplateHelper::custom_class_init<gi::register_type<self_type>>(
klass, nullptr);
}
public:
Gtk::Button hello_;
void on_hello(Gtk::Button) { std::cout << "Hi" << std::endl; }
void on_hello_tail(Gtk::Button b, Gtk::Label l)
{
g_assert(l.gobj_type_() == GTK_TYPE_LABEL);
g_assert(b.gobj_type_() == GTK_TYPE_BUTTON);
std::cout << "Hi tail" << std::endl;
}
void on_hello_head(Gtk::Label l, Gtk::Button b)
{
g_assert(l.gobj_type_() == GTK_TYPE_LABEL);
g_assert(b.gobj_type_() == GTK_TYPE_BUTTON);
std::cout << "Hi head" << std::endl;
}
FooWidget(const InitData &id)
: Gtk::impl::BoxImpl(this, id, "FooWidget"),
WidgetTemplateHelper(object_())
{
// skip registration case
if (!id) {
// as we have defined a get_type_() below, that should not happen
g_assert_not_reached();
return;
}
// locate object
hello_ = gi::object_cast<Gtk::Button>(
get_template_child(gobj_type_(), "hello_button"));
// setup signal
// NOTE the "manual" signature here should suitably match
// (also considering object/swapped flags, etc)
auto ok = set_handler<void(Gtk::Button)>(
"hello_button_clicked", gi::mem_fun(&self_type::on_hello, this));
g_assert(ok);
ok = set_handler<void(Gtk::Button, Gtk::Label), ConnectObject::TAIL>(
"hello_button_clicked_object",
gi::mem_fun(&self_type::on_hello_tail, this));
g_assert(ok);
ok = set_handler<void(Gtk::Label, Gtk::Button), ConnectObject::HEAD>(
"hello_button_clicked_object_swapped",
gi::mem_fun(&self_type::on_hello_head, this));
g_assert(ok);
}
static GType get_type_()
{
return register_type_<FooWidget>("FooWidget", 0, {}, {}, {});
}
};
class ExampleWindow : public Gtk::impl::WindowImpl
{
using self_type = ExampleWindow;
public:
ExampleWindow(Gtk::Window base, Gtk::Builder builder)
: Gtk::impl::WindowImpl(base, this, "ExampleWindow")
// custom name parameter is optional, but can be provided if used in .ui
{
(void)builder;
setup();
}
// name parameter is required and specifies registered type as-is
// (so some proper namespace prefix is advisable in real case)
ExampleWindow(const InitData &id)
: Gtk::impl::WindowImpl(this, id, "ExampleWindow")
{
// skip registration case
if (id)
setup();
}
void setup()
{
auto actions = Gio::SimpleActionGroup::new_();
auto am = Gio::ActionMap(actions);
auto action = Gio::SimpleAction::new_("help");
action.signal_activate().connect(gi::mem_fun(&self_type::on_help, this));
am.add_action(action);
insert_action_group("win", actions);
}
void on_help(Gio::Action, GLib::Variant) { std::cout << "Help" << std::endl; }
// subclass_type == 0; UI specifies GtkWindow
// subclass_type < 0; UI specifies GIOBJECT__ExampleWindow
// in either cases above; manual C++ association is needed
// (uses first constructor)
// -> NOT recommended
// subclass_type > 0; UI specifies ExampleWindow;
// all is properly created by (C-side) instance construction
// (uses second constructor)
// -> recommended
// subclass_type > 1; also insert a FooWidget
// (also all created by C-side construction)
static Gtk::Window build(int subclass_type)
{
const char *UIFILE = G_STRINGIFY(EXAMPLES_DIR) "/gtk-builder.ui";
const char *WINID = "window1";
auto builder = Gtk::Builder::new_();
// if the subtype has additional functionality (e.g. method overrides)
// then builder should instantiate using that type,
// otherwise base type suffices
// NOTE in the former case, there is a "short time" that the GObject side
// exists without a corresponding C++ side, so any attempt to use the
// extended parts (e.g. method calls) will be unfortunate
if (subclass_type) {
std::string WINCLASS = "GtkWindow";
std::ifstream f(UIFILE, std::ios::binary);
std::string ui;
std::getline(f, ui, '\0');
auto index = ui.find(WINCLASS);
assert(index != ui.npos);
ui.replace(ui.begin() + index, ui.begin() + index + WINCLASS.size(),
subclass_type < 0 ? "GIOBJECT__ExampleWindow" : "ExampleWindow");
if (subclass_type > 1) {
// sprinkle a template instance
std::string MARKER = "<!-- INSERT -->";
index = ui.find(MARKER);
ui.replace(ui.begin() + index, ui.begin() + index + MARKER.size(),
templ_child);
// ensure type registered
gi::register_type<FooWidget>();
}
// now we got a UI file that references the subclass type
// ensure the latter is registered
if (subclass_type < 0) {
// use a temporary instance
gi::make_ref<ExampleWindow, gi::construct_cpp_t>(nullptr, builder);
} else {
// a new cleaner way
gi::register_type<ExampleWindow>();
}
builder.add_from_string(ui, -1);
} else {
builder.add_from_file(UIFILE);
}
if (false) {
// some compile checks
builder.get_object(WINID);
builder.get_object<Gtk::Window>(WINID);
}
if (subclass_type > 1) {
// verify proper C++ side setup
auto foo_obj =
builder.get_object<FooWidget::baseclass_type>("foowidget1");
assert(foo_obj);
auto foo = gi::ref_ptr_cast<FooWidget>(foo_obj);
assert(foo);
assert(foo->hello_);
}
// the special _derived may be needed to setup C++ side and associate
return subclass_type > 0 ? builder.get_object<Gtk::Window>(WINID)
: builder.get_object_derived<self_type>(WINID);
}
};
#endif // GI_CLASS_IMPL
int
main(int argc, char **argv)
{
#ifdef GTK4
(void)argc;
(void)argv;
gtk_init();
#else
gtk_init(&argc, &argv);
#endif
loop = GLib::MainLoop::new_();
// recommended general approach iso stack based
// too much vmethod calling which is not safe for plain case
Gtk::Window win;
#ifdef GI_CLASS_IMPL
// silly compile/link check
static_assert(gi::transfer_full.value == 1, "");
if (argc == gi::transfer_full.value) {
win = gi::make_ref<TreeViewFilterWindow>();
} else {
win = ExampleWindow::build(std::stoi(argv[1]));
}
// TODO auto-handle arg ignore ??
#ifdef GTK4
win.signal_close_request().connect([](Gtk::Window) {
loop.quit();
return true;
});
win.show();
#else
win.signal_destroy().connect([](Gtk::Widget) { loop.quit(); });
win.show_all();
#endif
#else // GI_CLASS_IMPL
(void)win;
#endif
loop.run();
}

View File

@@ -0,0 +1,31 @@
# Configuration file for EditorConfig, see https://EditorConfig.org
# Ignore any other files further up in the file system
root = true
# All files:
[*]
# Let git determine line ending: end_of_line = lf
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
# Markdown files: keep trailing space-pair as line-break
[*.md]
trim_trailing_whitespace = false
# Python scripts:
[*.py]
# YAML scripts:
[*.yml]
indent_size = 2
# Makefiles: Tab indentation (no size specified)
[Makefile]
indent_style = tab
# C, C++ source files:
[*.{h,hpp,c,cpp}]

View File

@@ -0,0 +1,26 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for CodeBlocks
*.cbp text eol=lf
*.workspace text eol=lf
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

View File

@@ -0,0 +1,97 @@
name: CI
env:
PROJECT: EXPECTED_LITE
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
gcc:
strategy:
matrix:
version: [8, 9, 10, 11]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install GCC ${{ matrix.version }}
run: sudo apt-get install -y gcc-${{ matrix.version }} g++-${{ matrix.version }}
- name: Configure tests
env:
CXX: g++-${{ matrix.version }}
run: cmake -S . -B build
-D CMAKE_BUILD_TYPE:STRING=Release
-D ${{ env.PROJECT }}_OPT_SELECT_NONSTD=ON
-D ${{ env.PROJECT }}_OPT_BUILD_TESTS=ON
-D ${{ env.PROJECT }}_OPT_BUILD_EXAMPLES=OFF
- name: Build tests
run: cmake --build build -j 4
- name: Run tests
working-directory: build
run: ctest --output-on-failure -j 4
clang:
strategy:
matrix:
version: [8, 9, 10, 11, 12]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Clang ${{ matrix.version }}
run: sudo apt-get install -y clang-${{ matrix.version }}
- name: Configure tests
env:
CXX: clang-${{ matrix.version }}
run: cmake -S . -B build
-D CMAKE_CXX_COMPILER=clang++
-D CMAKE_BUILD_TYPE:STRING=Release
-D ${{ env.PROJECT }}_OPT_SELECT_NONSTD=ON
-D ${{ env.PROJECT }}_OPT_BUILD_TESTS=ON
-D ${{ env.PROJECT }}_OPT_BUILD_EXAMPLES=OFF
- name: Build tests
run: cmake --build build -j 4
- name: Run tests
working-directory: build
run: ctest --output-on-failure -j 4
msvc:
strategy:
matrix:
os: [windows-2019, windows-2022]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Configure tests
run: cmake -S . -B build
-D ${{ env.PROJECT }}_OPT_SELECT_NONSTD=ON
-D ${{ env.PROJECT }}_OPT_BUILD_TESTS=ON
-D ${{ env.PROJECT }}_OPT_BUILD_EXAMPLES=OFF
- name: Build tests
run: cmake --build build --config Release -j 4
- name: Run tests
working-directory: build
run: ctest -C Release --output-on-failure -j 4

View File

@@ -0,0 +1,41 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
test/expected.t
# Build folder
/build/
# CodeBlocks IDE files
*.layout
# Visual Studio Code
/.vscode/
# Visual Studio
/.vs/

View File

@@ -0,0 +1,4 @@
[bugtraq]
url = https://github.com/martinmoene/expected-lite/issues/%BUGID%
number = true
logregex = "(\\s*(,|and)?\\s*#\\d+)+\n(\\d+)"

View File

@@ -0,0 +1,166 @@
os: linux
dist: trusty
sudo: false
group: travis_latest
language: c++
cache: ccache
addons:
apt:
sources: &apt_sources
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.5
- llvm-toolchain-precise-3.6
- llvm-toolchain-precise-3.7
- llvm-toolchain-precise-3.8
- llvm-toolchain-trusty-3.9
- llvm-toolchain-trusty-4.0
- llvm-toolchain-trusty-5.0
- llvm-toolchain-trusty-6.0
matrix:
include:
- os: linux
env: COMPILER=g++-4.8
compiler: gcc
addons: &gcc4_8
apt:
packages: ["g++-4.8", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=g++-4.9
compiler: gcc
addons: &gcc4_9
apt:
packages: ["g++-4.9", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=g++-5
compiler: gcc
addons: &gcc5
apt:
packages: ["g++-5", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=g++-6
compiler: gcc
addons: &gcc6
apt:
packages: ["g++-6", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=g++-7
compiler: gcc
addons: &gcc7
apt:
packages: ["g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=g++-8
compiler: gcc
addons: &gcc8
apt:
packages: ["g++-8", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-3.5
compiler: clang
addons: &clang3_5
apt:
packages: ["clang-3.5", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-3.6
compiler: clang
addons: &clang3_6
apt:
packages: ["clang-3.6", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-3.7
compiler: clang
addons: &clang3-7
apt:
packages: ["clang-3.7", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-3.8
compiler: clang
addons: &clang3_8
apt:
packages: ["clang-3.8", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-3.9
compiler: clang
addons: &clang3_9
apt:
packages: ["clang-3.9", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-4.0
compiler: clang
addons: &clang4_0
apt:
packages: ["clang-4.0", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-5.0
compiler: clang
addons: &clang5_0
apt:
packages: ["clang-5.0", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-6.0
compiler: clang
addons: &clang6_0
apt:
packages: ["clang-6.0", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: osx
osx_image: xcode7.3
compiler: clang
env: COMPILER='clang++'
- os: osx
osx_image: xcode8
compiler: clang
env: COMPILER='clang++'
- os: osx
osx_image: xcode9
compiler: clang
env: COMPILER='clang++'
- os: osx
osx_image: xcode10
compiler: clang
env: COMPILER='clang++'
allow_failures:
- env: COMPILER=clang++-3.5
fast_finish: true
script:
- export CXX=${COMPILER}
- JOBS=2 # Travis machines have 2 cores.
- mkdir build && cd build
- cmake -G "Unix Makefiles" -DEXPECTED_LITE_OPT_SELECT_NONSTD=ON -DEXPECTED_LITE_OPT_BUILD_TESTS=ON -DEXPECTED_LITE_OPT_BUILD_EXAMPLES=OFF ..
- cmake --build . -- -j${JOBS}
- ctest --output-on-failure -j${JOBS}

View File

@@ -0,0 +1,5 @@
Changes for expected lite
version 0.0 2016-03-13
- Initial code commit.

View File

@@ -0,0 +1,130 @@
# Copyright 2016-2018 by Martin Moene
#
# https://github.com/martinmoene/expected-lite
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
cmake_minimum_required( VERSION 3.5 FATAL_ERROR )
# expected-lite project and version, updated by script/update-version.py:
project(
expected_lite
VERSION 0.6.2
# DESCRIPTION "Expected objects in C++11 and later in a single-file header-only library"
# HOMEPAGE_URL "https://github.com/martinmoene/expected-lite"
LANGUAGES CXX )
# Package information:
set( unit_name "expected" )
set( package_nspace "nonstd" )
set( package_name "${unit_name}-lite" )
set( package_version "${${PROJECT_NAME}_VERSION}" )
message( STATUS "Project '${PROJECT_NAME}', package '${package_name}' version: '${package_version}'")
# Toplevel or subproject:
if ( CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME )
set( expected_IS_TOPLEVEL_PROJECT TRUE )
else()
set( expected_IS_TOPLEVEL_PROJECT FALSE )
endif()
# If toplevel project, enable building and performing of tests, disable building of examples:
option( EXPECTED_LITE_OPT_BUILD_TESTS "Build and perform expected-lite tests" ${expected_IS_TOPLEVEL_PROJECT} )
option( EXPECTED_LITE_OPT_BUILD_EXAMPLES "Build expected-lite examples" OFF )
set( EXPEXTED_P0323R "99" STRING "Specify proposal revision compatibility (99: latest)" )
option( EXPECTED_LITE_OPT_SELECT_STD "Select std::expected" OFF )
option( EXPECTED_LITE_OPT_SELECT_NONSTD "Select nonstd::expected" OFF )
# If requested, build and perform tests, build examples:
if ( EXPECTED_LITE_OPT_BUILD_TESTS )
enable_testing()
add_subdirectory( test )
endif()
if ( EXPECTED_LITE_OPT_BUILD_EXAMPLES )
add_subdirectory( example )
endif()
#
# Interface, installation and packaging
#
# CMake helpers:
include( GNUInstallDirs )
include( CMakePackageConfigHelpers )
# Interface library:
add_library(
${package_name} INTERFACE )
add_library(
${package_nspace}::${package_name} ALIAS ${package_name} )
target_include_directories(
${package_name}
INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" )
# Package configuration:
# Note: package_name and package_target are used in package_config_in
set( package_folder "${package_name}" )
set( package_target "${package_name}-targets" )
set( package_config "${package_name}-config.cmake" )
set( package_config_in "${package_name}-config.cmake.in" )
set( package_config_version "${package_name}-config-version.cmake" )
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/${package_config_in}"
"${CMAKE_CURRENT_BINARY_DIR}/${package_config}"
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${package_folder}"
)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/${package_config_version}.in"
"${CMAKE_CURRENT_BINARY_DIR}/${package_config_version}" @ONLY
)
# Installation:
install(
TARGETS ${package_name}
EXPORT ${package_target}
# INCLUDES DESTINATION "${...}" # already set via target_include_directories()
)
install(
EXPORT ${package_target}
NAMESPACE ${package_nspace}::
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${package_folder}"
)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/${package_config}"
"${CMAKE_CURRENT_BINARY_DIR}/${package_config_version}"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${package_folder}"
)
install(
DIRECTORY "include/"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
export(
EXPORT ${package_target}
NAMESPACE ${package_nspace}::
FILE "${CMAKE_CURRENT_BINARY_DIR}/${package_name}-targets.cmake"
)
# end of file

View File

@@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,9 @@
Plan
----
- [ ] Implement expected.then() etc.
- [x] Write CMake files
- [x] Check code against current Dxxxxr0 and adapt
- [x] Expand README.md
- [x] Expand test/expected.t.cpp
- [x] Improve use of travis matrix

View File

@@ -0,0 +1,459 @@
# expected lite: expected objects for C++11 and later
[![Language](https://img.shields.io/badge/C%2B%2B-11-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![License](https://img.shields.io/badge/license-BSL-blue.svg)](https://opensource.org/licenses/BSL-1.0) [![Build Status](https://github.com/martinmoene/expected-lite/actions/workflows/ci.yml/badge.svg)](https://github.com/martinmoene/expected-lite/actions/workflows/ci.yml) [![Build Status](https://travis-ci.org/martinmoene/expected-lite.svg?branch=master)](https://travis-ci.org/martinmoene/expected-lite) [![Build status](https://ci.appveyor.com/api/projects/status/sle31w7obrm8lhe1?svg=true)](https://ci.appveyor.com/project/martinmoene/expected-lite) [![Version](https://badge.fury.io/gh/martinmoene%2Fexpected-lite.svg)](https://github.com/martinmoene/expected-lite/releases) [![download](https://img.shields.io/badge/latest-download-blue.svg)](https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp) [![Conan](https://img.shields.io/badge/on-conan-blue.svg)](https://conan.io/center/expected-lite) [![Try it online](https://img.shields.io/badge/on-wandbox-blue.svg)](https://wandbox.org/permlink/MnnwqOtE8ZQ4rRsv) [![Try it on godbolt online](https://img.shields.io/badge/on-godbolt-blue.svg)](https://godbolt.org/z/9BuMZx)
*expected lite* is a single-file header-only library for objects that either represent a valid value or an error that you can pass by value. It is intended for use with C++11 and later. The library is based on the [std:&#58;expected](http://wg21.link/p0323) proposal [1] .
**Contents**
- [Example usage](#example-usage)
- [In a nutshell](#in-a-nutshell)
- [License](#license)
- [Dependencies](#dependencies)
- [Installation](#installation)
- [Synopsis](#synopsis)
- [Comparison with like types](#comparison)
- [Reported to work with](#reported-to-work-with)
- [Implementation notes](#implementation-notes)
- [Other implementations of expected](#other-implementations-of-expected)
- [Notes and references](#notes-and-references)
- [Appendix](#appendix)
## Example usage
```Cpp
#include "nonstd/expected.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
using namespace nonstd;
using namespace std::literals;
auto to_int( char const * const text ) -> expected<int, std::string>
{
char * pos = nullptr;
auto value = strtol( text, &pos, 0 );
if ( pos != text ) return value;
else return make_unexpected( "'"s + text + "' isn't a number" );
}
int main( int argc, char * argv[] )
{
auto text = argc > 1 ? argv[1] : "42";
auto ei = to_int( text );
if ( ei ) std::cout << "'" << text << "' is " << *ei << ", ";
else std::cout << "Error: " << ei.error();
}
```
### Compile and run
```
prompt> g++ -std=c++14 -Wall -I../include -o 01-basic.exe 01-basic.cpp && 01-basic.exe 123 && 01-basic.exe abc
'123' is 123, Error: 'abc' isn't a number
```
## In a nutshell
**expected lite** is a single-file header-only library to represent value objects that either contain a valid value or an error. The library is a partly implementation of the proposal for [std:&#58;expected](http://wg21.link/p0323) [1,2,3] for use with C++11 and later.
**Some Features and properties of expected lite** are ease of installation (single header), default and explicit construction of an expected, construction and assignment from a value that is convertible to the underlying type, copy- and move-construction and copy- and move-assignment from another expected of the same type, testing for the presence of a value, operators for unchecked access to the value or the error (pointer or reference), value() and value_or() for checked access to the value, relational operators, swap() and various factory functions.
*expected lite* shares the approach to in-place tags with [any-lite](https://github.com/martinmoene/any-lite), [optional-lite](https://github.com/martinmoene/optional-lite) and with [variant-lite](https://github.com/martinmoene/variant-lite) and these libraries can be used together.
**Not provided** are reference-type expecteds. *expected lite* doesn't honour triviality of value and error types. *expected lite* doesn't handle overloaded *address of* operators.
For more examples, see [1].
## License
*expected lite* is distributed under the [Boost Software License](https://github.com/martinmoene/XXXX-lite/blob/master/LICENSE.txt).
## Dependencies
*expected lite* has no other dependencies than the [C++ standard library](http://en.cppreference.com/w/cpp/header).
## Installation
*expected lite* is a single-file header-only library. Put `expected.hpp` directly into the project source tree or somewhere reachable from your project.
## Synopsis
**Contents**
- [Configuration](#configuration)
- [Types in namespace nonstd](#types-in-namespace-nonstd)
- [Interface of expected](#interface-of-expected)
- [Algorithms for expected](#algorithms-for-expected)
- [Interface of unexpected_type](#interface-of-unexpected_type)
- [Algorithms for unexpected_type](#algorithms-for-unexpected_type)
### Configuration
#### Tweak header
If the compiler supports [`__has_include()`](https://en.cppreference.com/w/cpp/preprocessor/include), *expected lite* supports the [tweak header](https://vector-of-bool.github.io/2020/10/04/lib-configuration.html) mechanism. Provide your *tweak header* as `nonstd/expected.tweak.hpp` in a folder in the include-search-path. In the tweak header, provide definitions as documented below, like `#define expected_CPLUSPLUS 201103L`.
#### Standard selection macro
\-D<b>nsel\_CPLUSPLUS</b>=199711L
Define this macro to override the auto-detection of the supported C++ standard, or if your compiler does not set the `__cplusplus` macro correctly.
#### Select `std::expected` or `nonstd::expected`
At default, *expected lite* uses `std::expected` if it is available and lets you use it via namespace `nonstd`. You can however override this default and explicitly request to use `std::expected` or expected lite's `nonstd::expected` as `nonstd::expected` via the following macros.
-D<b>nsel\_CONFIG\_SELECT\_EXPECTED</b>=nsel_EXPECTED_DEFAULT
Define this to `nsel_EXPECTED_STD` to select `std::expected` as `nonstd::expected`. Define this to `nsel_EXPECTED_NONSTD` to select `nonstd::expected` as `nonstd::expected`. Default is undefined, which has the same effect as defining to `nsel_EXPECTED_DEFAULT`.
-D<b>nsel\_P0323R</b>=7 *(default)*
Define this to the proposal revision number to control the presence and behavior of features (see tables). Default is 7 for the latest revision.
#### Disable C++ exceptions
-D<b>nsel\_CONFIG\_NO\_EXCEPTIONS</b>=0
Define this to 1 if you want to compile without exceptions. If not defined, the header tries and detect if exceptions have been disabled (e.g. via `-fno-exceptions` or `/kernel`). Default determined in header.
#### Enable SEH exceptions
-D<b>nsel\_CONFIG\_NO\_EXCEPTIONS\_SEH</b>=0
Define this to 1 or 0 to control the use of SEH when C++ exceptions are disabled (see above). If not defined, the header tries and detect if SEH is available if C++ exceptions have been disabled (e.g. via `-fno-exceptions` or `/kernel`). Default determined in header.
#### Enable compilation errors
\-D<b>nsel\_CONFIG\_CONFIRMS\_COMPILATION\_ERRORS</b>=0
Define this macro to 1 to experience the by-design compile-time errors of the library in the test suite. Default is 0.
### Types in namespace nonstd
| Purpose | Type | Note / Object |
|-----------------|------|---------------|
| Expected | template&lt;typename T, typename E = std::exception_ptr><br>class **expected**; | nsel_P0323 <= 2 |
| Expected | template&lt;typename T, typename E><br>class **expected**; | nsel_P0323 > 2 |
| Error type | template&lt;typename E><br>class **unexpected_type**; | &nbsp; |
| Error type | template&lt;><br>class **unexpected_type**&lt;std::exception_ptr>; | nsel_P0323 <= 2 |
| Error type | template&lt;typename E><br>class **unexpected**; | >= C++17 |
| Traits | template&lt;typename E><br>struct **is_unexpected**; | nsel_P0323 <= 3 |
| In-place value construction | struct **in_place_t**; | in_place_t in_place{}; |
| In-place error construction | struct **in_place_unexpected_t**; | in_place_unexpected_t<br>unexpect{}; |
| In-place error construction | struct **in_place_unexpected_t**; | in_place_unexpected_t<br>in_place_unexpected{}; |
| Error reporting | class **bad_expected_access**; |&nbsp; |
### Interface of expected
| Kind | Method | Result |
|--------------|-------------------------------------------------------------------------|--------|
| Construction | [constexpr] **expected**() noexcept(...) | an object with default value |
| &nbsp; | [constexpr] **expected**( expected const & other ) | initialize to contents of other |
| &nbsp; | [constexpr] **expected**( expected && other ) | move contents from other |
| &nbsp; | [constexpr] **expected**( value_type const & value ) | initialize to value |
| &nbsp; | [constexpr] **expected**( value_type && value ) noexcept(...) | move from value |
| &nbsp; | [constexpr] explicit **expected**( in_place_t, Args&&... args ) | construct value in-place from args |
| &nbsp; | [constexpr] explicit **expected**( in_place_t,<br>&emsp;std::initializer_list&lt;U> il, Args&&... args ) | construct value in-place from args |
| &nbsp; | [constexpr] **expected**( unexpected_type<E> const & error ) | initialize to error |
| &nbsp; | [constexpr] **expected**( unexpected_type<E> && error ) | move from error |
| &nbsp; | [constexpr] explicit **expected**( in_place_unexpected_t,<br>&emsp;Args&&... args ) | construct error in-place from args |
| &nbsp; | [constexpr] explicit **expected**( in_place_unexpected_t,<br>&emsp;std::initializer_list&lt;U> il, Args&&... args )| construct error in-place from args |
| Destruction | ~**expected**() | destruct current content |
| Assignment | expected **operator=**( expected const & other ) | assign contents of other;<br>destruct current content, if any |
| &nbsp; | expected & **operator=**( expected && other ) noexcept(...) | move contents of other |
| &nbsp; | expected & **operator=**( U && v ) | move value from v |
| &nbsp; | expected & **operator=**( unexpected_type<E> const & u ) | initialize to unexpected |
| &nbsp; | expected & **operator=**( unexpected_type<E> && u ) | move from unexpected |
| &nbsp; | template&lt;typename... Args><br>void **emplace**( Args &&... args ) | emplace from args |
| &nbsp; | template&lt;typename U, typename... Args><br>void **emplace**( std::initializer_list&lt;U> il, Args &&... args ) | emplace from args |
| Swap | void **swap**( expected & other ) noexcept | swap with other |
| Observers | constexpr value_type const \* **operator->**() const | pointer to current content (const);<br>must contain value |
| &nbsp; | value_type \* **operator->**() | pointer to current content (non-const);<br>must contain value |
| &nbsp; | constexpr value_type const & **operator \***() const & | the current content (const ref);<br>must contain value |
| &nbsp; | constexpr value_type && **operator \***() && | the current content (non-const ref);<br>must contain value |
| &nbsp; | constexpr explicit operator **bool**() const noexcept | true if contains value |
| &nbsp; | constexpr **has_value**() const noexcept | true if contains value |
| &nbsp; | constexpr value_type const & **value**() const & | current content (const ref);<br>see [note 1](#note1) |
| &nbsp; | value_type & **value**() & | current content (non-const ref);<br>see [note 1](#note1) |
| &nbsp; | constexpr value_type && **value**() && | move from current content;<br>see [note 1](#note1) |
| &nbsp; | constexpr error_type const & **error**() const & | current error (const ref);<br>must contain error |
| &nbsp; | error_type & **error**() & | current error (non-const ref);<br>must contain error |
| &nbsp; | constexpr error_type && **error**() && | move from current error;<br>must contain error |
| &nbsp; | constexpr unexpected_type<E> **get_unexpected**() const | the error as unexpected&lt;>;<br>must contain error |
| &nbsp; | template&lt;typename Ex><br>bool **has_exception**() const | true of contains exception (as base) |
| &nbsp; | value_type **value_or**( U && v ) const & | value or move from v |
| &nbsp; | value_type **value_or**( U && v ) && | move from value or move from v |
| &nbsp; | ... | &nbsp; |
<a id="note1"></a>Note 1: checked access: if no content, for std::exception_ptr rethrows error(), otherwise throws bad_expected_access(error()).
### Algorithms for expected
| Kind | Function |
|---------------------------------|----------|
| Comparison with expected | &nbsp; |
| ==&ensp;!= | template&lt;typename T1, typename E1, typename T2, typename E2><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T1,E1> const & x,<br>&emsp;expected&lt;T2,E2> const & y ) |
| Comparison with expected | nsel_P0323R <= 2 |
| <&ensp;>&ensp;<=&ensp;>= | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T,E> const & x,<br>&emsp;expected&lt;T,E> const & y ) |
| Comparison with unexpected_type | &nbsp; |
| ==&ensp;!= | template&lt;typename T1, typename E1, typename E2><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T1,E1> const & x,<br>&emsp;unexpected_type&lt;E2> const & u ) |
| &nbsp; | template&lt;typename T1, typename E1, typename E2><br>constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;E2> const & u,<br>&emsp;expected&lt;T1,E1> const & x ) |
| Comparison with unexpected_type | nsel_P0323R <= 2 |
| <&ensp;>&ensp;<=&ensp;>= | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T,E> const & x,<br>&emsp;unexpected_type&lt;E> const & u ) |
| &nbsp; | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;E> const & u,<br>&emsp;expected&lt;T,E> const & x ) |
| Comparison with T | &nbsp; |
| ==&ensp;!= | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T,E> const & x,<br>&emsp;T const & v ) |
| &nbsp; | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;T const & v,<br>&emsp;expected&lt;T,E> const & x ) |
| Comparison with T | nsel_P0323R <= 2 |
| <&ensp;>&ensp;<=&ensp;>= | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T,E> const & x,<br>&emsp;T const & v ) |
| &nbsp; | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;T const & v,<br>&emsp;expected&lt;T,E> const & x ) |
| Specialized algorithms | &nbsp; |
| Swap | template&lt;typename T, typename E><br>void **swap**(<br>&emsp;expected&lt;T,E> & x,<br>&emsp;expected&lt;T,E> & y )&emsp;noexcept( noexcept( x.swap(y) ) ) |
| Make expected from | nsel_P0323R <= 3 |
| &emsp;Value | template&lt;typename T><br>constexpr auto **make_expected**( T && v ) -><br>&emsp;expected< typename std::decay&lt;T>::type> |
| &emsp;Nothing | auto **make_expected**() -> expected&lt;void> |
| &emsp;Current exception | template&lt;typename T><br>constexpr auto **make_expected_from_current_exception**() -> expected&lt;T> |
| &emsp;Exception | template&lt;typename T><br>auto **make_expected_from_exception**( std::exception_ptr v ) -> expected&lt;T>|
| &emsp;Error | template&lt;typename T, typename E><br>constexpr auto **make_expected_from_error**( E e ) -><br>&emsp;expected&lt;T, typename std::decay&lt;E>::type> |
| &emsp;Call | template&lt;typename F><br>auto **make_expected_from_call**( F f ) -><br>&emsp;expected< typename std::result_of&lt;F()>::type>|
| &emsp;Call, void specialization | template&lt;typename F><br>auto **make_expected_from_call**( F f ) -> expected&lt;void> |
### Interface of unexpected_type
| Kind | Method | Result |
|--------------|-----------------------------------------------------------|--------|
| Construction | **unexpected_type**() = delete; | no default construction |
| &nbsp; | constexpr explicit **unexpected_type**( E const & error ) | copy-constructed from an E |
| &nbsp; | constexpr explicit **unexpected_type**( E && error ) | move-constructed from an E |
| Observers | constexpr error_type const & **value**() const | can observe contained error |
| &nbsp; | error_type & **value**() | can modify contained error |
### Algorithms for unexpected_type
| Kind | Function |
|-------------------------------|----------|
| Comparison with unexpected | &nbsp; |
| ==&ensp;!= | template&lt;typename E><br>constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;E> const & x,<br>&emsp;unexpected_type&lt;E> const & y ) |
| Comparison with unexpected | nsel_P0323R <= 2 |
| <&ensp;>&ensp;<=&ensp;>= | template&lt;typename E><br>constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;E> const & x,<br>&emsp;unexpected_type&lt;E> const & y ) |
| Comparison with exception_ptr | &nbsp; |
| ==&ensp;!= | constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;std::exception_ptr> const & x,<br>&emsp;unexpected_type&lt;std::exception_ptr> const & y ) |
| Comparison with exception_ptr | nsel_P0323R <= 2 |
| <&ensp;>&ensp;<=&ensp;>= | constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;std::exception_ptr> const & x,<br>&emsp;unexpected_type&lt;std::exception_ptr> const & y ) |
| Specialized algorithms | &nbsp; |
| Make unexpected from | &nbsp; |
| &emsp;Error | template&lt;typename E><br>[constexpr] auto **make_unexpected**( E && v) -><br>&emsp;unexpected_type< typename std::decay&lt;E>::type>|
| Make unexpected from | nsel_P0323R <= 3 |
| &emsp;Current exception | [constexpr] auto **make_unexpected_from_current_exception**() -><br>&emsp;unexpected_type< std::exception_ptr>|
<a id="comparison"></a>
## Comparison with like types
|Feature |<br>std::pair|std:: optional |std:: expected |nonstd:: expected |Boost. Expected |Nonco expected |Andrei Expected |Hagan required |
|----------------------|-------------|---------------|---------------|------------------|----------------|---------------|----------------|---------------|
|More information | see [14] | see [5] | see [1] | this work | see [4] | see [7] | see [8] | see [13] |
| | | | | | | | | |
| C++03 | yes | no | no | no/not yet | no (union) | no | no | yes |
| C++11 | yes | no | no | yes | yes | yes | yes | yes |
| C++14 | yes | no | no | yes | yes | yes | yes | yes |
| C++17 | yes | yes | no | yes | yes | yes | yes | yes |
| | | | | | | | | |
|DefaultConstructible | T param | yes | yes | yes | yes | no | no | no |
|In-place construction | no | yes | yes | yes | yes | yes | no | no |
|Literal type | yes | yes | yes | yes | yes | no | no | no |
| | | | | | | | | |
|Disengaged information| possible | no | yes | yes | yes | yes | yes | no |
|Vary disengaged type | yes | no | yes | yes | yes | no | no | no |
|Engaged nonuse throws | no | no | no | no | error_traits | no | no | yes |
|Disengaged use throws | no | yes, value() | yes, value() | yes, value() | yes,<br>value()| yes,<br>get() | yes,<br>get() | n/a |
| | | | | | | | | |
|Proxy (rel.ops) | no | yes | yes | yes | yes | no | no | no |
|References | no | yes | no/not yet | no/not yet | no/not yet | yes | no | no |
|Chained visitor(s) | no | no | yes | maybe | yes | no | no | no |
Note 1: std:&#58;*experimental*:&#58;expected
Note 2: sources for [Nonco expected](https://github.com/martinmoene/spike-expected/tree/master/nonco), [Andrei Expected](https://github.com/martinmoene/spike-expected/tree/master/alexandrescu) and [Hagan required](https://github.com/martinmoene/spike-expected/tree/master/hagan) can befound in the [spike-expected](https://github.com/martinmoene/spike-expected) repository.
## Reported to work with
TBD
## Implementation notes
TBD
## Other implementations of expected
- Simon Brand. [C++11/14/17 std::expected with functional-style extensions](https://github.com/TartanLlama/expected). Single-header.
- Isabella Muerte. [MNMLSTC Core](https://github.com/mnmlstc/core) (C++11).
- Vicente J. Botet Escriba. [stdmake's expected](https://github.com/viboes/std-make/tree/master/include/experimental/fundamental/v3/expected) (C++17).
- Facebook. [ Folly's Expected.h](https://github.com/facebook/folly/blob/master/folly/Expected.h) (C++14).
## Notes and references
[1] Vicente J. Botet Escriba. [p0323 - A proposal to add a utility class to represent expected object (latest)](http://wg21.link/p0323) (HTML). ([r10](http://wg21.link/p0323r10), [r9](http://wg21.link/p0323r9), [r8](http://wg21.link/p0323r8), [r7](http://wg21.link/p0323r7), [r6](http://wg21.link/p0323r6), [r5](http://wg21.link/p0323r5), [r4](http://wg21.link/p0323r4), [r3](http://wg21.link/p0323r3), [r2](http://wg21.link/p0323r2), [r1](http://wg21.link/n4109), [r0](http://wg21.link/n4015), [draft](https://github.com/viboes/std-make/blob/master/doc/proposal/expected/DXXXXR0_expected.pdf)).
[2] Vicente J. Botet Escriba. [JASEL: Just a simple experimental library for C++](https://github.com/viboes/std-make). Reference implementation of [expected](https://github.com/viboes/std-make/tree/master/include/experimental/fundamental/v3/expected).
[3] Vicente J. Botet Escriba. [Expected - An exception-friendly Error Monad](https://www.youtube.com/watch?v=Zdlt1rgYdMQ). C++Now 2014. 24 September 2014.
[4] Pierre Talbot. [Boost.Expected. Unofficial Boost candidate](http://www.google-melange.com/gsoc/proposal/review/google/gsoc2013/trademark/25002). 5 May 2013. [GitHub](https://github.com/TrademarkPewPew/Boost.Expected), [GSoC 2013 Proposal](http://www.google-melange.com/gsoc/proposal/review/google/gsoc2013/trademark/25002), [boost@lists.boost.org](http://permalink.gmane.org/gmane.comp.lib.boost.devel/240056 ).
[5] Fernando Cacciola and Andrzej Krzemieński. [A proposal to add a utility class to represent optional objects (Revision 4)](http://isocpp.org/files/papers/N3672.html). ISO/IEC JTC1 SC22 WG21 N3672 2013-04-19.
[6] Andrzej Krzemieński, [Optional library implementation in C++11](https://github.com/akrzemi1/Optional/).
[7] Anto Nonco. [Extending expected<T> to deal with references](http://anto-nonco.blogspot.nl/2013/03/extending-expected-to-deal-with.html). 27 May 2013.
[8] Andrei Alexandrescu. Systematic Error Handling in C++. Prepared for The C++and Beyond Seminar 2012. [Video](http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C). [Slides](http://sdrv.ms/RXjNPR).
[9] Andrei Alexandrescu. [Choose your Poison: Exceptions or Error Codes? (PDF)](http://accu.org/content/conf2007/Alexandrescu-Choose_Your_Poison.pdf). ACCU Conference 2007.
[10] Andrei Alexandrescu. [The Power of None (PPT)](http://nwcpp.org/static/talks/2006/The_Power_of_None.ppt). Northwest C++ Users' Group. [May 17th, 2006](http://nwcpp.org/may-2006.html).
[11] Jon Jagger. [A Return Type That Doesn't Like Being Ignored](http://accu.org/var/uploads/journals/overload53-FINAL.pdf#page=18). Overload issue 53, February 2003.
[12] Andrei Alexandrescu. [Error Handling in C++: Are we inching towards a total solution?](http://accu.org/index.php/conferences/2002/speakers2002). ACCU Conference 2002.
[13] Ken Hagan et al. [Exploding return codes](https://groups.google.com/d/msg/comp.lang.c++.moderated/BkZqPfoq3ys/H_PMR8Sat4oJ). comp.lang.c++.moderated. 11 February 2000.
[14] [std::pair](http://en.cppreference.com/w/cpp/utility/pair). cppreference.com
[15] Niall Douglas. [Outcome](https://ned14.github.io/outcome/). Very lightweight outcome&lt;T> and result&lt;T> (non-Boost edition).
[16] Niall Douglas. [p0762 - Concerns about expected&lt;T, E> from the Boost.Outcome peer review](http://wg21.link/p0762). 15 October 2017.
## Appendix
### A.1 Compile-time information
The version of *expected lite* is available via tag `[.version]`. The following tags are available for information on the compiler and on the C++ standard library used: `[.compiler]`, `[.stdc++]`, `[.stdlanguage]` and `[.stdlibrary]`.
### A.2 Expected lite test specification
<details>
<summary>click to expand</summary>
<p>
```Text
unexpected_type: Disallows default construction
unexpected_type: Allows to copy-construct from unexpected_type, default
unexpected_type: Allows to move-construct from unexpected_type, default
unexpected_type: Allows to in-place-construct
unexpected_type: Allows to in-place-construct from initializer_list
unexpected_type: Allows to copy-construct from error_type
unexpected_type: Allows to move-construct from error_type
unexpected_type: Allows to copy-construct from unexpected_type, explicit converting
unexpected_type: Allows to copy-construct from unexpected_type, non-explicit converting
unexpected_type: Allows to move-construct from unexpected_type, explicit converting
unexpected_type: Allows to move-construct from unexpected_type, non-explicit converting
unexpected_type: Allows to copy-assign from unexpected_type, default
unexpected_type: Allows to move-assign from unexpected_type, default
unexpected_type: Allows to copy-assign from unexpected_type, converting
unexpected_type: Allows to move-assign from unexpected, converting
unexpected_type: Allows to observe its value via a l-value reference
unexpected_type: Allows to observe its value via a r-value reference
unexpected_type: Allows to modify its value via a l-value reference
unexpected_type: Allows to be swapped
unexpected_type<std::exception_ptr>: Disallows default construction
unexpected_type<std::exception_ptr>: Allows to copy-construct from error_type
unexpected_type<std::exception_ptr>: Allows to move-construct from error_type
unexpected_type<std::exception_ptr>: Allows to copy-construct from an exception
unexpected_type<std::exception_ptr>: Allows to observe its value
unexpected_type<std::exception_ptr>: Allows to modify its value
unexpected_type: Provides relational operators
unexpected_type: Provides relational operators, std::exception_ptr specialization
make_unexpected(): Allows to create an unexpected_type<E> from an E
unexpected: C++17 and later provide unexpected_type as unexpected
bad_expected_access: Disallows default construction
bad_expected_access: Allows construction from error_type
bad_expected_access: Allows to observe its error
bad_expected_access: Allows to change its error
bad_expected_access: Provides non-empty what()
expected: Allows to default construct
expected: Allows to copy-construct from expected: value
expected: Allows to copy-construct from expected: error
expected: Allows to move-construct from expected: value
expected: Allows to move-construct from expected: error
expected: Allows to copy-construct from expected; value, explicit converting
expected: Allows to copy-construct from expected; error, explicit converting
expected: Allows to copy-construct from expected; value, non-explicit converting
expected: Allows to copy-construct from expected; error, non-explicit converting
expected: Allows to move-construct from expected; value, explicit converting
expected: Allows to move-construct from expected; error, explicit converting
expected: Allows to move-construct from expected; value, non-explicit converting
expected: Allows to move-construct from expected; error, non-explicit converting
expected: Allows to forward-construct from value, explicit converting
expected: Allows to forward-construct from value, non-explicit converting
expected: Allows to in-place-construct value
expected: Allows to in-place-construct value from initializer_list
expected: Allows to copy-construct from unexpected, explicit converting
expected: Allows to copy-construct from unexpected, non-explicit converting
expected: Allows to move-construct from unexpected, explicit converting
expected: Allows to move-construct from unexpected, non-explicit converting
expected: Allows to in-place-construct error
expected: Allows to in-place-construct error from initializer_list
expected: Allows to copy-assign from expected, value
expected: Allows to copy-assign from expected, error
expected: Allows to move-assign from expected, value
expected: Allows to move-assign from expected, error
expected: Allows to forward-assign from value
expected: Allows to copy-assign from unexpected
expected: Allows to move-assign from unexpected
expected: Allows to move-assign from move-only unexpected
expected: Allows to emplace value
expected: Allows to emplace value from initializer_list
expected: Allows to be swapped
expected: Allows to observe its value via a pointer
expected: Allows to observe its value via a pointer to constant
expected: Allows to modify its value via a pointer
expected: Allows to observe its value via a l-value reference
expected: Allows to observe its value via a r-value reference
expected: Allows to modify its value via a l-value reference
expected: Allows to modify its value via a r-value reference
expected: Allows to observe if it contains a value (or error)
expected: Allows to observe its value
expected: Allows to modify its value
expected: Allows to move its value
expected: Allows to observe its error
expected: Allows to modify its error
expected: Allows to move its error
expected: Allows to observe its error as unexpected
expected: Allows to query if it contains an exception of a specific base type
expected: Allows to observe its value if available, or obtain a specified value otherwise
expected: Allows to move its value if available, or obtain a specified value otherwise
expected: Throws bad_expected_access on value access when disengaged
expected<void>: Allows to default-construct
expected<void>: Allows to copy-construct from expected<void>: value
expected<void>: Allows to copy-construct from expected<void>: error
expected<void>: Allows to move-construct from expected<void>: value
expected<void>: Allows to move-construct from expected<void>: error
expected<void>: Allows to in-place-construct
expected<void>: Allows to copy-construct from unexpected, explicit converting
expected<void>: Allows to copy-construct from unexpected, non-explicit converting
expected<void>: Allows to move-construct from unexpected, explicit converting
expected<void>: Allows to move-construct from unexpected, non-explicit converting
expected<void>: Allows to in-place-construct unexpected_type
expected<void>: Allows to in-place-construct error from initializer_list
expected<void>: Allows to copy-assign from expected, value
expected<void>: Allows to copy-assign from expected, error
expected<void>: Allows to move-assign from expected, value
expected<void>: Allows to move-assign from expected, error
expected<void>: Allows to emplace value
expected<void>: Allows to be swapped
expected<void>: Allows to observe if it contains a value (or error)
expected<void>: Allows to observe its value
expected<void>: Allows to observe its error
expected<void>: Allows to modify its error
expected<void>: Allows to move its error
expected<void>: Allows to observe its error as unexpected
expected<void>: Allows to query if it contains an exception of a specific base type
expected<void>: Throws bad_expected_access on value access when disengaged
operators: Provides expected relational operators
swap: Allows expected to be swapped
std::hash: Allows to compute hash value for expected
tweak header: reads tweak header if supported [tweak]
```
</p>
</details>

View File

@@ -0,0 +1,71 @@
version: "{branch} #{build}"
shallow_clone: true
image:
- Visual Studio 2019
- Visual Studio 2017
- Visual Studio 2015
platform:
- Win32
- x64
configuration:
- Debug
- Release
build:
parallel: true
environment:
matrix:
- generator: "Visual Studio 16 2019"
select_sv: -DEXPECTED_LITE_OPT_SELECT_STD=ON
- generator: "Visual Studio 16 2019"
select_sv: -DEXPECTED_LITE_OPT_SELECT_NONSTD=ON
- generator: "Visual Studio 15 2017"
select_sv: -DEXPECTED_LITE_OPT_SELECT_STD=ON
- generator: "Visual Studio 15 2017"
select_sv: -DEXPECTED_LITE_OPT_SELECT_NONSTD=ON
- generator: "Visual Studio 14 2015"
# - generator: "Visual Studio 12 2013"
# - generator: "Visual Studio 11 2012"
# - generator: "Visual Studio 10 2010"
matrix:
exclude:
- image: Visual Studio 2015
generator: "Visual Studio 16 2019"
- image: Visual Studio 2019
generator: "Visual Studio 15 2017"
- image: Visual Studio 2019
generator: "Visual Studio 14 2015"
- image: Visual Studio 2019
generator: "Visual Studio 12 2013"
- image: Visual Studio 2019
generator: "Visual Studio 11 2012"
- image: Visual Studio 2019
generator: "Visual Studio 10 2010"
- image: Visual Studio 2015
generator: "Visual Studio 15 2017"
- image: Visual Studio 2017
generator: "Visual Studio 16 2019"
- image: Visual Studio 2017
generator: "Visual Studio 14 2015"
- image: Visual Studio 2017
generator: "Visual Studio 12 2013"
- image: Visual Studio 2017
generator: "Visual Studio 11 2012"
- image: Visual Studio 2017
generator: "Visual Studio 10 2010"
before_build:
- mkdir build && cd build
- cmake -A %platform% -G "%generator%" "%select_sv%" -DEXPECTED_LITE_OPT_BUILD_TESTS=ON -DEXPECTED_LITE_OPT_BUILD_EXAMPLES=OFF ..
build_script:
- cmake --build . --config %configuration%
test_script:
- ctest --output-on-failure -C %configuration%

View File

@@ -0,0 +1,24 @@
# Adapted from write_basic_package_version_file(... COMPATIBILITY SameMajorVersion) output
# ARCH_INDEPENDENT is only present in cmake 3.14 and onwards
set( PACKAGE_VERSION "@package_version@" )
if( PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION )
set( PACKAGE_VERSION_COMPATIBLE FALSE )
else()
if( "@package_version@" MATCHES "^([0-9]+)\\." )
set( CVF_VERSION_MAJOR "${CMAKE_MATCH_1}" )
else()
set( CVF_VERSION_MAJOR "@package_version@" )
endif()
if( PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR )
set( PACKAGE_VERSION_COMPATIBLE TRUE )
else()
set( PACKAGE_VERSION_COMPATIBLE FALSE )
endif()
if( PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION )
set( PACKAGE_VERSION_EXACT TRUE )
endif()
endif()

View File

@@ -0,0 +1,7 @@
@PACKAGE_INIT@
# Only include targets once:
if( NOT TARGET @package_name@::@package_name@ )
include( "${CMAKE_CURRENT_LIST_DIR}/@package_target@.cmake" )
endif()

View File

@@ -0,0 +1,27 @@
from conans import ConanFile, CMake
class ExpectedLiteConan(ConanFile):
version = "0.6.2"
name = "expected-lite"
description = "Expected objects for C++11 and later"
license = "Boost Software License - Version 1.0. http://www.boost.org/LICENSE_1_0.txt"
url = "https://github.com/martinmoene/expected-lite.git"
exports_sources = "include/nonstd/*", "CMakeLists.txt", "cmake/*", "LICENSE.txt"
settings = "compiler", "build_type", "arch"
build_policy = "missing"
author = "Martin Moene"
def build(self):
"""Avoid warning on build step"""
pass
def package(self):
"""Run CMake install"""
cmake = CMake(self)
cmake.definitions["EXPECTED_LITE_OPT_BUILD_TESTS"] = "OFF"
cmake.definitions["EXPECTED_LITE_OPT_BUILD_EXAMPLES"] = "OFF"
cmake.configure()
cmake.install()
def package_info(self):
self.info.header_only()

View File

@@ -0,0 +1,33 @@
// Convert text to number and yield expected with number or error text.
#include "nonstd/expected.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
using namespace nonstd;
using namespace std::literals;
auto to_int( char const * const text ) -> expected<int, std::string>
{
char * pos = nullptr;
auto value = strtol( text, &pos, 0 );
if ( pos != text ) return value;
else return make_unexpected( "'"s + text + "' isn't a number" );
}
int main( int argc, char * argv[] )
{
auto text = argc > 1 ? argv[1] : "42";
auto ei = to_int( text );
if ( ei ) std::cout << "'" << text << "' is " << *ei << ", ";
else std::cout << "Error: " << ei.error();
}
// cl -EHsc -wd4814 -I../include 01-basic.cpp && 01-basic.exe 123 && 01-basic.exe abc
// g++ -std=c++14 -Wall -I../include -o 01-basic.exe 01-basic.cpp && 01-basic.exe 123 && 01-basic.exe abc
// '123' is 123, Error: 'abc' isn't a number

View File

@@ -0,0 +1,68 @@
// Use a non-ignorable value with expected.
#include "nonstd/expected.hpp"
#include <iostream>
using namespace nonstd;
template< typename T >
class required
{
public:
required( T const & value)
: content( value ) {}
required( required && other )
: content( other.content )
, ignored( other.ignored )
{
other.ignored = false;
}
required( required const & other ) = delete;
~required() noexcept( false )
{
if ( ignored )
throw std::runtime_error("required: content unobserved");
};
T const & operator *() const { ignored = false; return content; }
private:
T content;
mutable bool ignored = true;
};
template< typename T >
auto make_required( T value ) -> required<T>
{
return required<T>( std::move(value) );
}
using unused_type = char;
auto produce( int value ) -> expected< required<int>, unused_type >
{
return make_required( std::move(value) );
}
int main( int argc, char * argv[] )
{
try
{
auto er42 = produce( 42 );
auto er13 = produce( 13 );
std::cout << "value: " << **er42 << "\n";
}
catch ( std::exception const & e )
{
std::cout << "Error: " << e.what();
}
}
// cl -EHsc -wd4814 -Zc:implicitNoexcept- -I../include 02-required.cpp && 02-required.exe
// g++ -std=c++14 -Wall -I../include -o 02-required.exe 02-required.cpp && 02-required.exe
// value: 42
// Error: required: content unobserved

View File

@@ -0,0 +1,77 @@
// Copyright (c) 2016-2020 Martin Moene
//
// https://github.com/martinmoene/expected-lite
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "nonstd/expected.hpp"
#include <iostream>
template< typename T >
void use( T const & /*x*/) {}
#define expected_PRESENT( x ) \
std::cout << #x << ": " << x << "\n"
#define expected_ABSENT( x ) \
std::cout << #x << ": (undefined)\n"
void report()
{
#ifdef __cpp_exceptions
expected_PRESENT( __cpp_exceptions );
#else
expected_ABSENT( __cpp_exceptions );
#endif
#ifdef __EXCEPTIONS
expected_PRESENT( __EXCEPTIONS );
#else
expected_ABSENT( __EXCEPTIONS );
#endif
#ifdef _HAS_EXCEPTIONS
expected_PRESENT( _HAS_EXCEPTIONS );
#else
expected_ABSENT( _HAS_EXCEPTIONS );
#endif
#ifdef _CPPUNWIND
expected_PRESENT( _CPPUNWIND );
#else
expected_ABSENT( _CPPUNWIND );
#endif
}
int violate_access()
{
nonstd::expected<int, char> eu( nonstd:: make_unexpected('a') );
return eu.value();
}
int main()
{
report();
#if ! nsel_CONFIG_NO_EXCEPTIONS_SEH
return violate_access();
#else
__try
{
return violate_access();
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
std::cerr << "\n*** Executing SEH __except block ***\n";
}
#endif
}
// -Dnsel_CONFIG_NO_EXCEPTIONS=1 automatically determined in expected.hpp
// -Dnsel_CONFIG_NO_EXCEPTIONS_SEH=0 default:1 for msvc
// cl -nologo -kernel -EHs-c- -GR- -I../include 03-no-exceptions.cpp && 03-no-exceptions
// cl -nologo -kernel -EHs-c- -GR- -Dnsel_CONFIG_NO_EXCEPTIONS_SEH=0 -I../include 03-no-exceptions.cpp && 03-no-exceptions
// g++ -Wall -fno-exceptions -I../include -o 03-no-exceptions 03-no-exceptions.cpp && 03-no-exceptions

View File

@@ -0,0 +1,79 @@
# Copyright (c) 2016-2022 Martin Moene.
#
# https://github.com/martinmoene/expected-lite
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
if( NOT DEFINED CMAKE_MINIMUM_REQUIRED_VERSION )
cmake_minimum_required( VERSION 3.8 FATAL_ERROR )
endif()
project( example LANGUAGES CXX )
set( unit_name "expected" )
set( PACKAGE ${unit_name}-lite )
message( STATUS "Subproject '${PROJECT_NAME}', various examples")
# Target default options and definitions:
set( OPTIONS "" )
#set( DEFINITIONS "" )
# Sources (.cpp), normal and no-exception, and their base names:
set( SOURCES_CPP11
02-required.cpp
)
set( SOURCES_CPP14
01-basic.cpp
)
# note: here variable must be quoted to create semicolon separated list:
string( REPLACE ".cpp" "" BASENAMES_CPP11 "${SOURCES_CPP11}" )
string( REPLACE ".cpp" "" BASENAMES_CPP14 "${SOURCES_CPP14}" )
set( TARGETS_CPP11 ${BASENAMES_CPP11} )
set( TARGETS_CPP14 ${BASENAMES_CPP14} )
set( TARGETS_ALL ${TARGETS_CPP11} ${TARGETS_CPP14} )
# add targets:
foreach( name ${TARGETS_ALL} )
add_executable( ${name} ${name}.cpp )
target_link_libraries( ${name} PRIVATE ${PACKAGE} )
endforeach()
# set compiler options:
if( ${CMAKE_GENERATOR} MATCHES Visual )
foreach( name ${TARGETS_ALL} )
target_compile_options( ${name} PUBLIC -W3 -EHsc -wd4814 -Zc:implicitNoexcept- )
endforeach()
else()
foreach( name ${TARGETS_ALL} )
target_compile_options( ${name} PUBLIC -Wall )
endforeach()
foreach( name ${TARGETS_CPP11} )
target_compile_options( ${name} PUBLIC -std=c++11 )
endforeach()
foreach( name ${TARGETS_CPP14} )
target_compile_options( ${name} PUBLIC -std=c++14 )
endforeach()
endif()
# configure unit tests via CTest:
enable_testing()
foreach( name ${TARGETS_ALL} )
add_test ( NAME ${name} COMMAND ${name} )
set_property( TEST ${name} PROPERTY LABELS example )
endforeach()
# end of file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<CodeBlocks_project_file>
<FileVersion major="1" minor="6" />
<Project>
<Option title="Expected Lite" />
<Option pch_mode="2" />
<Option compiler="gcc" />
<Build>
<Target title="Release">
<Option output="bin/Release/optional3" prefix_auto="1" extension_auto="1" />
<Option object_output="obj/Release/" />
<Option type="1" />
<Option compiler="gcc" />
<Compiler>
<Add option="-O2" />
</Compiler>
<Linker>
<Add option="-s" />
</Linker>
</Target>
</Build>
<Compiler>
<Add option="-Wall" />
</Compiler>
<Unit filename="../../.gitattributes" />
<Unit filename="../../.gitignore" />
<Unit filename="../../.travis.yml" />
<Unit filename="../../CHANGES.txt" />
<Unit filename="../../CMakeLists.txt" />
<Unit filename="../../LICENSE.txt" />
<Unit filename="../../Notes.md" />
<Unit filename="../../README.md" />
<Unit filename="../../appveyor.yml" />
<Unit filename="../../cmake/expected-lite-config-version.cmake.in" />
<Unit filename="../../cmake/expected-lite-config.cmake.in" />
<Unit filename="../../conanfile.py" />
<Unit filename="../../example/01-basic.cpp" />
<Unit filename="../../example/02-required.cpp" />
<Unit filename="../../example/CMakeLists.txt" />
<Unit filename="../../include/nonstd/expected.hpp" />
<Unit filename="../../script/create-cov-rpt.py" />
<Unit filename="../../script/create-vcpkg.py" />
<Unit filename="../../script/update-version.py" />
<Unit filename="../../script/upload-conan.py" />
<Unit filename="../../test/CMakeLists.txt" />
<Unit filename="../../test/expected-main.t.cpp" />
<Unit filename="../../test/expected-main.t.hpp" />
<Unit filename="../../test/expected.t.cpp" />
<Unit filename="../../test/lest.hpp" />
<Unit filename="../../test/odr.cpp" />
<Unit filename="../../test/t-odr.bat" />
<Unit filename="../../test/t.bat" />
<Unit filename="../../test/tc.bat" />
<Unit filename="../../test/tg-all.bat" />
<Unit filename="../../test/tg.bat" />
<Extensions>
<code_completion />
<envvars />
<debugger />
<lib_finder disable_auto="1" />
</Extensions>
</Project>
</CodeBlocks_project_file>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<CodeBlocks_workspace_file>
<Workspace title="Workspace">
<Project filename="expected.cbp" />
<Project filename="../../optional-lite/project/optional.cbp" />
<Project filename="../../expected-spike/codeblocks.cbp" />
<Project filename="../../../GitHub-Pre/IsoCpp/gsl-lite/project/gsl-lite.cbp" />
</Workspace>
</CodeBlocks_workspace_file>

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python
#
# Copyright 2019-2019 by Martin Moene
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# script/create-cov-rpt.py, Python 3.4 and later
#
import argparse
import os
import re
import sys
import subprocess
# Configuration:
cfg_github_project = 'expected-lite'
cfg_github_user = 'martinmoene'
cfg_prj_folder_level = 3
tpl_coverage_cmd = 'opencppcoverage --no_aggregate_by_file --sources {src} -- {exe}'
# End configuration.
def project_folder( f, args ):
"""Project root"""
if args.prj_folder:
return args.prj_folder
return os.path.normpath( os.path.join( os.path.dirname( os.path.abspath(f) ), '../' * args.prj_folder_level ) )
def executable_folder( f ):
"""Folder where the xecutable is"""
return os.path.dirname( os.path.abspath(f) )
def executable_name( f ):
"""Folder where the executable is"""
return os.path.basename( f )
def createCoverageReport( f, args ):
print( "Creating coverage report for project '{usr}/{prj}', '{file}':".
format( usr=args.user, prj=args.project, file=f ) )
cmd = tpl_coverage_cmd.format( folder=executable_folder(f), src=project_folder(f, args), exe=executable_name(f) )
if args.verbose:
print( "> {}".format(cmd) )
if not args.dry_run:
os.chdir( executable_folder(f) )
subprocess.call( cmd, shell=False )
os.chdir( project_folder(f, args) )
def createCoverageReports( args ):
for f in args.executable:
createCoverageReport( f, args )
def createCoverageReportFromCommandLine():
"""Collect arguments from the commandline and create coverage report."""
parser = argparse.ArgumentParser(
description='Create coverage report.',
epilog="""""",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
'executable',
metavar='executable',
type=str,
nargs=1,
help='executable to report on')
parser.add_argument(
'-n', '--dry-run',
action='store_true',
help='do not execute conan commands')
parser.add_argument(
'-v', '--verbose',
action='count',
default=0,
help='level of progress reporting')
parser.add_argument(
'--user',
metavar='u',
type=str,
default=cfg_github_user,
help='github user name')
parser.add_argument(
'--project',
metavar='p',
type=str,
default=cfg_github_project,
help='github project name')
parser.add_argument(
'--prj-folder',
metavar='f',
type=str,
default=None,
help='project root folder')
parser.add_argument(
'--prj-folder-level',
metavar='n',
type=int,
default=cfg_prj_folder_level,
help='project root folder level from executable')
createCoverageReports( parser.parse_args() )
if __name__ == '__main__':
createCoverageReportFromCommandLine()
# end of file

View File

@@ -0,0 +1,223 @@
#!/usr/bin/env python
#
# Copyright 2019-2019 by Martin Moene
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# script/upload-conan.py, Python 3.4 and later
#
import argparse
import os
import re
import sys
import subprocess
# Configuration:
cfg_github_project = 'expected-lite'
cfg_github_user = 'martinmoene'
cfg_description = '(unused)'
cfg_cmakelists = 'CMakeLists.txt'
cfg_readme = 'Readme.md'
cfg_license = 'LICENSE.txt'
cfg_ref_prefix = 'v'
cfg_sha512 = 'dadeda'
cfg_vcpkg_description = '(no description found)'
cfg_vcpkg_root = os.environ['VCPKG_ROOT']
cfg_cmake_optpfx = "EXPECTED_LITE"
# End configuration.
# vcpkg control and port templates:
tpl_path_vcpkg_control = '{vcpkg}/ports/{prj}/CONTROL'
tpl_path_vcpkg_portfile = '{vcpkg}/ports/{prj}/portfile.cmake'
tpl_vcpkg_control =\
"""Source: {prj}
Version: {ver}
Description: {desc}"""
tpl_vcpkg_portfile =\
"""include(vcpkg_common_functions)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO {usr}/{prj}
REF {ref}
SHA512 {sha}
)
vcpkg_configure_cmake(
SOURCE_PATH ${{SOURCE_PATH}}
PREFER_NINJA
OPTIONS
-D{optpfx}_OPT_BUILD_TESTS=OFF
-D{optpfx}_OPT_BUILD_EXAMPLES=OFF
)
vcpkg_install_cmake()
vcpkg_fixup_cmake_targets(
CONFIG_PATH lib/cmake/${{PORT}}
)
file(REMOVE_RECURSE
${{CURRENT_PACKAGES_DIR}}/debug
${{CURRENT_PACKAGES_DIR}}/lib
)
file(INSTALL
${{SOURCE_PATH}}/{lic} DESTINATION ${{CURRENT_PACKAGES_DIR}}/share/${{PORT}} RENAME copyright
)"""
tpl_vcpkg_note_sha =\
"""
Next actions:
- Obtain package SHA: 'vcpkg install {prj}', copy SHA mentioned in 'Actual hash: [...]'
- Add SHA to package: 'script\create-vcpkg --sha={sha}'
- Install package : 'vcpkg install {prj}'"""
tpl_vcpkg_note_install =\
"""
Next actions:
- Install package: 'vcpkg install {prj}'"""
# End of vcpkg templates
def versionFrom( filename ):
"""Obtain version from CMakeLists.txt"""
with open( filename, 'r' ) as f:
content = f.read()
version = re.search(r'VERSION\s(\d+\.\d+\.\d+)', content).group(1)
return version
def descriptionFrom( filename ):
"""Obtain description from CMakeLists.txt"""
with open( filename, 'r' ) as f:
content = f.read()
description = re.search(r'DESCRIPTION\s"(.*)"', content).group(1)
return description if description else cfg_vcpkg_description
def vcpkgRootFrom( path ):
return path if path else './vcpkg'
def to_ref( version ):
"""Add prefix to version/tag, like v1.2.3"""
return cfg_ref_prefix + version
def control_path( args ):
"""Create path like vcpks/ports/_project_/CONTROL"""
return tpl_path_vcpkg_control.format( vcpkg=args.vcpkg_root, prj=args.project )
def portfile_path( args ):
"""Create path like vcpks/ports/_project_/portfile.cmake"""
return tpl_path_vcpkg_portfile.format( vcpkg=args.vcpkg_root, prj=args.project )
def createControl( args ):
"""Create vcpkg CONTROL file"""
output = tpl_vcpkg_control.format(
prj=args.project, ver=args.version, desc=args.description )
if args.verbose:
print( "Creating control file '{f}':".format( f=control_path( args ) ) )
if args.verbose > 1:
print( output )
os.makedirs( os.path.dirname( control_path( args ) ), exist_ok=True )
with open( control_path( args ), 'w') as f:
print( output, file=f )
def createPortfile( args ):
"""Create vcpkg portfile"""
output = tpl_vcpkg_portfile.format(
optpfx=cfg_cmake_optpfx, usr=args.user, prj=args.project, ref=to_ref(args.version), sha=args.sha, lic=cfg_license )
if args.verbose:
print( "Creating portfile '{f}':".format( f=portfile_path( args ) ) )
if args.verbose > 1:
print( output )
os.makedirs( os.path.dirname( portfile_path( args ) ), exist_ok=True )
with open( portfile_path( args ), 'w') as f:
print( output, file=f )
def printNotes( args ):
if args.sha == cfg_sha512:
print( tpl_vcpkg_note_sha.
format( prj=args.project, sha='...' ) )
else:
print( tpl_vcpkg_note_install.
format( prj=args.project ) )
def createVcpkg( args ):
print( "Creating vcpkg for '{usr}/{prj}', version '{ver}' in folder '{vcpkg}':".
format( usr=args.user, prj=args.project, ver=args.version, vcpkg=args.vcpkg_root, ) )
createControl( args )
createPortfile( args )
printNotes( args )
def createVcpkgFromCommandLine():
"""Collect arguments from the commandline and create vcpkg."""
parser = argparse.ArgumentParser(
description='Create microsoft vcpkg.',
epilog="""""",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
'-v', '--verbose',
action='count',
default=0,
help='level of progress reporting')
parser.add_argument(
'--user',
metavar='u',
type=str,
default=cfg_github_user,
help='github user name')
parser.add_argument(
'--project',
metavar='p',
type=str,
default=cfg_github_project,
help='github project name')
parser.add_argument(
'--description',
metavar='d',
type=str,
# default=cfg_description,
default=descriptionFrom( cfg_cmakelists ),
help='vcpkg description [from ' + cfg_cmakelists + ']')
parser.add_argument(
'--version',
metavar='v',
type=str,
default=versionFrom( cfg_cmakelists ),
help='version number [from ' + cfg_cmakelists + ']')
parser.add_argument(
'--sha',
metavar='s',
type=str,
default=cfg_sha512,
help='sha of package')
parser.add_argument(
'--vcpkg-root',
metavar='r',
type=str,
default=vcpkgRootFrom( cfg_vcpkg_root ),
help='parent folder containing ports to write files to')
createVcpkg( parser.parse_args() )
if __name__ == '__main__':
createVcpkgFromCommandLine()
# end of file

View File

@@ -0,0 +1,129 @@
#!/usr/bin/env python
#
# Copyright 2017-2018 by Martin Moene
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# script/update-version.py
#
from __future__ import print_function
import argparse
import os
import re
import sys
# Configuration:
table = (
# path, substitute find, substitute format
( 'CMakeLists.txt'
, r'\W{2,4}VERSION\W+([0-9]+\.[0-9]+\.[0-9]+)\W*$'
, ' VERSION {major}.{minor}.{patch}' )
, ( 'CMakeLists.txt'
, r'set\W+expected_lite_version\W+"([0-9]+\.[0-9]+\.[0-9]+)"\W+$'
, 'set( expected_lite_version "{major}.{minor}.{patch}" )\n' )
# , ( 'example/cmake-pkg/CMakeLists.txt'
# , r'set\W+expected_lite_version\W+"([0-9]+\.[0-9]+(\.[0-9]+)?)"\W+$'
# , 'set( expected_lite_version "{major}.{minor}" )\n' )
#
# , ( 'script/install-xxx-pkg.py'
# , r'\expected_lite_version\s+=\s+"([0-9]+\.[0-9]+\.[0-9]+)"\s*$'
# , 'expected_lite_version = "{major}.{minor}.{patch}"\n' )
, ( 'conanfile.py'
, r'version\s+=\s+"([0-9]+\.[0-9]+\.[0-9]+)"\s*$'
, 'version = "{major}.{minor}.{patch}"' )
, ( 'include/nonstd/expected.hpp'
, r'\#define\s+expected_lite_MAJOR\s+[0-9]+\s*$'
, '#define expected_lite_MAJOR {major}' )
, ( 'include/nonstd/expected.hpp'
, r'\#define\s+expected_lite_MINOR\s+[0-9]+\s*$'
, '#define expected_lite_MINOR {minor}' )
, ( 'include/nonstd/expected.hpp'
, r'\#define\s+expected_lite_PATCH\s+[0-9]+\s*$'
, '#define expected_lite_PATCH {patch}\n' )
)
# End configuration.
def readFile( in_path ):
"""Return content of file at given path"""
with open( in_path, 'r' ) as in_file:
contents = in_file.read()
return contents
def writeFile( out_path, contents ):
"""Write contents to file at given path"""
with open( out_path, 'w' ) as out_file:
out_file.write( contents )
def replaceFile( output_path, input_path ):
# prevent race-condition (Python 3.3):
if sys.version_info >= (3, 3):
os.replace( output_path, input_path )
else:
os.remove( input_path )
os.rename( output_path, input_path )
def editFileToVersion( version, info, verbose ):
"""Update version given file path, version regexp and new version format in info"""
major, minor, patch = version.split('.')
in_path, ver_re, ver_fmt = info
out_path = in_path + '.tmp'
new_text = ver_fmt.format( major=major, minor=minor, patch=patch )
if verbose:
print( "- {path} => '{text}':".format( path=in_path, text=new_text.strip('\n') ) )
writeFile(
out_path,
re.sub(
ver_re, new_text, readFile( in_path )
, count=0, flags=re.MULTILINE
)
)
replaceFile( out_path, in_path )
def editFilesToVersion( version, table, verbose ):
if verbose:
print( "Editing files to version {v}:".format(v=version) )
for item in table:
editFileToVersion( version, item, verbose )
def editFilesToVersionFromCommandLine():
"""Update version number given on command line in paths from configuration table."""
parser = argparse.ArgumentParser(
description='Update version number in files.',
epilog="""""",
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
'version',
metavar='version',
type=str,
nargs=1,
help='new version number, like 1.2.3')
parser.add_argument(
'-v', '--verbose',
action='store_true',
help='report the name of the file being processed')
args = parser.parse_args()
editFilesToVersion( args.version[0], table, args.verbose )
if __name__ == '__main__':
editFilesToVersionFromCommandLine()
# end of file

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python
#
# Copyright 2019-2019 by Martin Moene
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# script/upload-conan.py
#
from __future__ import print_function
import argparse
import os
import re
import sys
import subprocess
# Configuration:
def_conan_project = 'expected-lite'
def_conan_user = 'nonstd-lite'
def_conan_channel = 'stable'
cfg_conanfile = 'conanfile.py'
tpl_conan_create = 'conan create . {usr}/{chn}'
tpl_conan_upload = 'conan upload --remote {usr} {prj}/{ver}@{usr}/{chn}'
# End configuration.
def versionFrom( filename ):
"""Obtain version from conanfile.py"""
with open( filename ) as f:
content = f.read()
version = re.search(r'version\s=\s"(.*)"', content).group(1)
return version
def createConanPackage( args ):
"""Create conan package and upload it."""
cmd = tpl_conan_create.format(usr=args.user, chn=args.channel)
if args.verbose:
print( "> {}".format(cmd) )
if not args.dry_run:
subprocess.call( cmd, shell=False )
def uploadConanPackage( args ):
"""Create conan package and upload it."""
cmd = tpl_conan_upload.format(prj=args.project, usr=args.user, chn=args.channel, ver=args.version)
if args.verbose:
print( "> {}".format(cmd) )
if not args.dry_run:
subprocess.call( cmd, shell=False )
def uploadToConan( args ):
"""Create conan package and upload it."""
print( "Updating project '{prj}' to user '{usr}', channel '{chn}', version {ver}:".
format(prj=args.project, usr=args.user, chn=args.channel, ver=args.version) )
createConanPackage( args )
uploadConanPackage( args )
def uploadToConanFromCommandLine():
"""Collect arguments from the commandline and create conan package and upload it."""
parser = argparse.ArgumentParser(
description='Create conan package and upload it to conan.',
epilog="""""",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
'-n', '--dry-run',
action='store_true',
help='do not execute conan commands')
parser.add_argument(
'-v', '--verbose',
action='count',
default=0,
help='level of progress reporting')
parser.add_argument(
'--project',
metavar='p',
type=str,
default=def_conan_project,
help='conan project')
parser.add_argument(
'--user',
metavar='u',
type=str,
default=def_conan_user,
help='conan user')
parser.add_argument(
'--channel',
metavar='c',
type=str,
default=def_conan_channel,
help='conan channel')
parser.add_argument(
'--version',
metavar='v',
type=str,
default=versionFrom( cfg_conanfile ),
help='version number [from conanfile.py]')
uploadToConan( parser.parse_args() )
if __name__ == '__main__':
uploadToConanFromCommandLine()
# end of file

View File

@@ -0,0 +1,233 @@
# Copyright 2016-2018 by Martin Moene
#
# https://github.com/martinmoene/expected-lite
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
if( NOT DEFINED CMAKE_MINIMUM_REQUIRED_VERSION )
cmake_minimum_required( VERSION 3.5 FATAL_ERROR )
endif()
project( test LANGUAGES CXX )
set( unit_name "expected" )
set( PACKAGE ${unit_name}-lite )
set( PROGRAM ${unit_name}-lite )
set( SOURCES ${unit_name}-main.t.cpp ${unit_name}.t.cpp )
set( TWEAKD "." )
message( STATUS "Subproject '${PROJECT_NAME}', programs '${PROGRAM}-*'")
set( OPTIONS "" )
set( DEFCMN "-Dlest_FEATURE_AUTO_REGISTER=1" )
set( HAS_STD_FLAGS FALSE )
set( HAS_CPP98_FLAG FALSE )
set( HAS_CPP11_FLAG FALSE )
set( HAS_CPP14_FLAG FALSE )
set( HAS_CPP17_FLAG FALSE )
set( HAS_CPP20_FLAG FALSE )
set( HAS_CPPLATEST_FLAG FALSE )
if ( EXPEXTED_P0323R LESS "99" )
set( DEFCMN ${DEFCMN} "-Dnsel_P0323R=${EXPEXTED_P0323R}" )
endif()
if( MSVC )
message( STATUS "Matched: MSVC")
set( HAS_STD_FLAGS TRUE )
set( OPTIONS -W3 -EHsc )
set( DEFINITIONS -D_SCL_SECURE_NO_WARNINGS ${DEFCMN} )
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.00 )
set( HAS_CPP14_FLAG TRUE )
set( HAS_CPPLATEST_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.11 )
set( HAS_CPP17_FLAG TRUE )
endif()
elseif( CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang" )
message( STATUS "CompilerId: '${CMAKE_CXX_COMPILER_ID}'")
set( HAS_STD_FLAGS TRUE )
set( HAS_CPP98_FLAG TRUE )
set( OPTIONS -Wall -Wextra -Wconversion -Wsign-conversion -Wno-missing-braces -fno-elide-constructors )
set( DEFINITIONS ${DEFCMN} )
# GNU: available -std flags depends on version
if( CMAKE_CXX_COMPILER_ID MATCHES "GNU" )
message( STATUS "Matched: GNU")
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8.0 )
set( HAS_CPP11_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9.2 )
set( HAS_CPP14_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.1.0 )
set( HAS_CPP17_FLAG TRUE )
endif()
# AppleClang: available -std flags depends on version
elseif( CMAKE_CXX_COMPILER_ID MATCHES "AppleClang" )
message( STATUS "Matched: AppleClang")
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0.0 )
set( HAS_CPP11_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.1.0 )
set( HAS_CPP14_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.2.0 )
set( HAS_CPP17_FLAG TRUE )
endif()
# Clang: available -std flags depends on version
elseif( CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
message( STATUS "Matched: Clang")
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.3.0 )
set( HAS_CPP11_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.4.0 )
set( HAS_CPP14_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0.0 )
set( HAS_CPP17_FLAG TRUE )
endif()
endif()
elseif( CMAKE_CXX_COMPILER_ID MATCHES "Intel" )
# as is
message( STATUS "Matched: Intel")
else()
# as is
message( STATUS "Matched: nothing")
endif()
# enable MS C++ Core Guidelines checker if MSVC:
function( enable_msvs_guideline_checker target )
if( MSVC )
set_target_properties( ${target} PROPERTIES
VS_GLOBAL_EnableCppCoreCheck true
VS_GLOBAL_CodeAnalysisRuleSet CppCoreCheckRules.ruleset
VS_GLOBAL_RunCodeAnalysis true )
endif()
endfunction()
# make target, compile for given standard if specified:
function( make_target target std )
message( STATUS "Make target: '${std}'" )
add_executable ( ${target} ${SOURCES} )
target_include_directories( ${target} SYSTEM PRIVATE lest )
target_include_directories( ${target} PRIVATE ${TWEAKD} )
target_link_libraries ( ${target} PRIVATE ${PACKAGE} )
target_compile_options ( ${target} PRIVATE ${OPTIONS} )
target_compile_definitions( ${target} PRIVATE ${DEFINITIONS} )
if( std )
if( MSVC )
target_compile_options( ${target} PRIVATE -std:c++${std} )
else()
# Necessary for clang 3.x:
target_compile_options( ${target} PRIVATE -std=c++${std} )
# Ok for clang 4 and later:
# set( CMAKE_CXX_STANDARD ${std} )
# set( CMAKE_CXX_STANDARD_REQUIRED ON )
# set( CMAKE_CXX_EXTENSIONS OFF )
endif()
endif()
endfunction()
# add generic executable, unless -std flags can be specified:
if( NOT HAS_STD_FLAGS )
make_target( ${PROGRAM}.t "" )
else()
# # unconditionally add C++98 variant as MSVC has no option for it:
# if( HAS_CPP98_FLAG )
# make_target( ${PROGRAM}-cpp98.t 98 )
# else()
# make_target( ${PROGRAM}-cpp98.t "" )
# endif()
# unconditionally add C++11 variant as MSVC has no option for it:
if( HAS_CPP11_FLAG )
make_target( ${PROGRAM}-cpp11.t 11 )
else()
make_target( ${PROGRAM}-cpp11.t "" )
endif()
if( HAS_CPP14_FLAG )
make_target( ${PROGRAM}-cpp14.t 14 )
endif()
if( HAS_CPP17_FLAG )
set( std17 17 )
if( CMAKE_CXX_COMPILER_ID MATCHES "AppleClang" )
set( std17 1z )
endif()
make_target( ${PROGRAM}-cpp17.t ${std17} )
enable_msvs_guideline_checker( ${PROGRAM}-cpp17.t )
endif()
if( HAS_CPPLATEST_FLAG )
make_target( ${PROGRAM}-cpplatest.t latest )
endif()
endif()
# with C++20, honour explicit request for std::expected or nonstd::expected:
if( HAS_CPP20_FLAG )
set( WHICH nsel_EXPECTED_DEFAULT )
if( EXPECTED_LITE_OPT_SELECT_STD )
set( WHICH nsel_EXPECTED_STD )
elseif( EXPECTED_LITE_OPT_SELECT_NONSTD )
set( WHICH nsel_EXPECTED_NONSTD )
endif()
target_compile_definitions( ${PROGRAM}-cpp17.t PRIVATE nsel_CONFIG_SELECT_EXPECTED=${WHICH} )
if( HAS_CPPLATEST_FLAG )
target_compile_definitions( ${PROGRAM}-cpplatest.t PRIVATE nsel_CONFIG_SELECT_EXPECTED=${WHICH} )
endif()
endif()
# configure unit tests via CTest:
enable_testing()
if( HAS_STD_FLAGS )
# # unconditionally add C++98 variant for MSVC:
# add_test( NAME test-cpp98 COMMAND ${PROGRAM}-cpp98.t )
#
# unconditionally add C++11 variant for MSVC:
add_test( NAME test-cpp11 COMMAND ${PROGRAM}-cpp11.t )
if( HAS_CPP14_FLAG )
add_test( NAME test-cpp14 COMMAND ${PROGRAM}-cpp14.t )
endif()
if( HAS_CPP17_FLAG )
add_test( NAME test-cpp17 COMMAND ${PROGRAM}-cpp17.t )
endif()
if( HAS_CPPLATEST_FLAG )
add_test( NAME test-cpplatest COMMAND ${PROGRAM}-cpplatest.t )
endif()
else()
add_test( NAME test COMMAND ${PROGRAM}.t --pass )
add_test( NAME list_version COMMAND ${PROGRAM}.t --version )
add_test( NAME list_tags COMMAND ${PROGRAM}.t --list-tags )
add_test( NAME list_tests COMMAND ${PROGRAM}.t --list-tests )
endif()
# end of file

View File

@@ -0,0 +1,131 @@
// Copyright (c) 2016-2018 Martin Moene
//
// https://github.com/martinmoene/expected-lite
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "expected-main.t.hpp"
#define expected_PRESENT( x ) \
std::cout << #x << ": " << x << "\n"
#define expected_ABSENT( x ) \
std::cout << #x << ": (undefined)\n"
// Suppress:
// - unused parameter, for cases without assertions such as [.std...]
#if defined(__clang__)
# pragma clang diagnostic ignored "-Wunused-parameter"
#elif defined __GNUC__
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
lest::tests & specification()
{
static lest::tests tests;
return tests;
}
CASE( "expected-lite version" "[.expected][.version]" )
{
expected_PRESENT( expected_lite_MAJOR );
expected_PRESENT( expected_lite_MINOR );
expected_PRESENT( expected_lite_PATCH );
expected_PRESENT( expected_lite_VERSION );
}
CASE( "any configuration" "[.expected][.config]" )
{
expected_PRESENT( nsel_HAVE_STD_EXPECTED );
expected_PRESENT( nsel_USES_STD_EXPECTED );
expected_PRESENT( nsel_EXPECTED_DEFAULT );
expected_PRESENT( nsel_EXPECTED_NONSTD );
expected_PRESENT( nsel_EXPECTED_STD );
expected_PRESENT( nsel_CONFIG_SELECT_EXPECTED );
expected_PRESENT( nsel_CONFIG_NO_EXCEPTIONS );
expected_PRESENT( nsel_CPLUSPLUS );
}
CASE( "__cplusplus" "[.stdc++]" )
{
expected_PRESENT( __cplusplus );
#ifdef _MSVC_LANG
expected_PRESENT( _MSVC_LANG );
#else
expected_ABSENT( _MSVC_LANG );
#endif
}
CASE( "Compiler version" "[.compiler]" )
{
#if nsel_USES_STD_EXPECTED
std::cout << "(Compiler version not available: using std::expected)\n";
#else
expected_PRESENT( nsel_COMPILER_CLANG_VERSION );
expected_PRESENT( nsel_COMPILER_GNUC_VERSION );
expected_PRESENT( nsel_COMPILER_MSVC_VERSION );
#endif
}
CASE( "presence of C++ language features" "[.stdlanguage]" )
{
#if nsel_USES_STD_EXPECTED
std::cout << "(Presence of C++ language features not available: using std::expected)\n";
#else
std::cout << "[.stdlanguage]: none\n";
#endif
}
CASE( "presence of C++ library features" "[.stdlibrary]" )
{
#if nsel_USES_STD_EXPECTED
std::cout << "(Presence of C++ library features not available: using std::expected)\n";
#else
#ifdef __cpp_exceptions
expected_PRESENT( __cpp_exceptions );
#else
expected_ABSENT( __cpp_exceptions );
#endif
#ifdef __EXCEPTIONS
expected_PRESENT( __EXCEPTIONS );
#else
expected_ABSENT( __EXCEPTIONS );
#endif
#ifdef _HAS_EXCEPTIONS
expected_PRESENT( _HAS_EXCEPTIONS );
#else
expected_ABSENT( _HAS_EXCEPTIONS );
#endif
#ifdef _CPPUNWIND
expected_PRESENT( _CPPUNWIND );
#else
expected_ABSENT( _CPPUNWIND );
#endif
#endif
}
int main( int argc, char * argv[] )
{
return lest::run( specification(), argc, argv );
}
#if 0
g++ -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++98 -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++03 -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++0x -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++11 -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++14 -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++17 -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
cl -EHsc -I../include expected-lite.t.cpp && expected-lite.t.exe --pass
#endif
// end of file

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2016-2018 Martin Moene
//
// https://github.com/martinmoene/expected-lite
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#pragma once
#ifndef TEST_EXPECTED_LITE_H_INCLUDED
#define TEST_EXPECTED_LITE_H_INCLUDED
// Limit C++ Core Guidelines checking to expected-lite:
#include "nonstd/expected.hpp"
#if nsel_COMPILER_MSVC_VER >= 1910
# include <CppCoreCheck/Warnings.h>
# pragma warning(disable: ALL_CPPCORECHECK_WARNINGS)
#endif
#include <iosfwd>
namespace lest {
template< typename T, typename E >
std::ostream & operator<<( std::ostream & os, nonstd::expected<T,E> const & );
template< typename E >
std::ostream & operator<<( std::ostream & os, nonstd::expected<void,E> const & );
} // namespace lest
// Compiler warning suppression for usage of lest:
#ifdef __clang__
# pragma clang diagnostic ignored "-Wstring-conversion"
# pragma clang diagnostic ignored "-Wunused-parameter"
# pragma clang diagnostic ignored "-Wunused-template"
# pragma clang diagnostic ignored "-Wunused-function"
# pragma clang diagnostic ignored "-Wunused-member-function"
#elif defined __GNUC__
# pragma GCC diagnostic ignored "-Wunused-parameter"
# pragma GCC diagnostic ignored "-Wunused-function"
#endif
#include "lest.hpp"
#define CASE( name ) lest_CASE( specification(), name )
extern lest::tests & specification();
namespace lest {
// use oparator<< instead of to_string() overload;
// see http://stackoverflow.com/a/10651752/437272
template< typename T, typename E >
inline std::ostream & operator<<( std::ostream & os, nonstd::expected<T,E> const & v )
{
using lest::to_string;
return os << "[expected:" << (v ? to_string(*v) : "[empty]") << "]";
}
template< typename E >
inline std::ostream & operator<<( std::ostream & os, nonstd::expected<void,E> const & v )
{
using lest::to_string;
return os << "[expected<void>:" << (v ? "[non-empty]" : "[empty]") << "]";
}
} // namespace lest
#endif // TEST_EXPECTED_LITE_H_INCLUDED
// end of file

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2016-2020 Martin Moene
//
// https://github.com/martinmoene/expected-lite
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "nonstd/expected.hpp"
#include <iostream>
template< typename T >
void use( T const & /*x*/) {}
#define expected_PRESENT( x ) \
std::cout << #x << ": " << x << "\n"
#define expected_ABSENT( x ) \
std::cout << #x << ": (undefined)\n"
void report()
{
#ifdef __cpp_exceptions
expected_PRESENT( __cpp_exceptions );
#else
expected_ABSENT( __cpp_exceptions );
#endif
#ifdef __EXCEPTIONS
expected_PRESENT( __EXCEPTIONS );
#else
expected_ABSENT( __EXCEPTIONS );
#endif
#ifdef _HAS_EXCEPTIONS
expected_PRESENT( _HAS_EXCEPTIONS );
#else
expected_ABSENT( _HAS_EXCEPTIONS );
#endif
#ifdef _CPPUNWIND
expected_PRESENT( _CPPUNWIND );
#else
expected_ABSENT( _CPPUNWIND );
#endif
#ifdef _CPPRTTI
expected_PRESENT( _CPPRTTI );
#else
expected_ABSENT( _CPPRTTI );
#endif
}
int violate_access()
{
nonstd::expected<int, char> eu( nonstd:: make_unexpected('a') );
return eu.value();
}
int main()
{
report();
#if ! nsel_CONFIG_NO_EXCEPTIONS_SEH
return violate_access();
#else
__try
{
return violate_access();
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
std::cerr << "\n*** Executing SEH __except block ***\n";
}
#endif
}
// end of file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
#define EXPECTED_TWEAK_VALUE 42

View File

@@ -0,0 +1,3 @@
#include "nonstd/expected.hpp"
MAIN

View File

@@ -0,0 +1,63 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: t.bat - compile & run tests (MSVC).
::
set unit=expected
:: if no std is given, use compiler default
set std=%1
if not "%std%"=="" set std=-std:%std%
call :CompilerVersion version
echo VC%version%: %args%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=
set msvc_defines=^
-Dlest_FEATURE_AUTO_REGISTER=1 ^
-D_CRT_SECURE_NO_WARNINGS ^
-D_SCL_SECURE_NO_WARNINGS ^
-D_HAS_EXCEPTIONS=0 ^
-Dnsel_CONFIG_NO_SEH=0
:: -Dnsel_CONFIG_NO_EXCEPTIONS=1
set CppCoreCheckInclude=%VCINSTALLDIR%\Auxiliary\VS\include
:: -EHsc
::cl -kernel -GR- -W3 %std% %unit_select% %unit_config% %msvc_defines% -I"%CppCoreCheckInclude%" -I../include -I. %unit%-noexcept.t.cpp && %unit%-noexcept.t.exe
cl -EHs -GR- -W3 %std% %unit_select% %unit_config% %msvc_defines% -I"%CppCoreCheckInclude%" -Ilest -I../include -I. %unit%-noexcept.t.cpp && %unit%-noexcept.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
@echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> >%tmpsource%
echo int main(){printf("%%d\n",_MSC_VER);} >>%tmpsource%
cl /nologo %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
set offset=0
if %version% LSS 1900 set /a offset=1
set /a version="version / 10 - 10 * ( 5 + offset )"
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,3 @@
cl -EHsc -DMAIN="" -Dnsel_P0323R=-2 -I../include -I. -Foodr1.obj -c odr.cpp
cl -EHsc -DMAIN="int main(){}" -Dnsel_P0323R=-2 -I../include -I. -Foodr2.obj -c odr.cpp
cl odr1.obj odr2.obj

View File

@@ -0,0 +1,57 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: t.bat - compile & run tests (MSVC).
::
set unit=expected
:: if no std is given, use compiler default
set std=%1
if not "%std%"=="" set std=-std:%std%
call :CompilerVersion version
echo VC%version%: %args%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=
set msvc_defines=^
-Dlest_FEATURE_AUTO_REGISTER=1 ^
-D_CRT_SECURE_NO_WARNINGS ^
-D_SCL_SECURE_NO_WARNINGS
set CppCoreCheckInclude=%VCINSTALLDIR%\Auxiliary\VS\include
cl -nologo -W3 -EHsc %std% %unit_select% %unit_config% %msvc_defines% -I"%CppCoreCheckInclude%" -Ilest -I../include -I. %unit%-main.t.cpp %unit%.t.cpp && %unit%-main.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
@echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> >%tmpsource%
echo int main(){printf("%%d\n",_MSC_VER);} >>%tmpsource%
cl /nologo %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
set offset=0
if %version% LSS 1900 set /a offset=1
set /a version="version / 10 - 10 * ( 5 + offset )"
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,60 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: tc-cl.bat - compile & run tests (clang-cl).
::
set unit=expected
set unit_file=%unit%
:: if no std is given, use c++14
set std=c++14
if NOT "%1" == "" set std=%1 & shift
set UCAP=%unit%
call :toupper UCAP
set unit_select=%unit%_%UCAP%_NONSTD
::set unit_select=%unit%_CONFIG_SELECT_%UCAP%_NONSTD
if NOT "%1" == "" set unit_select=%1 & shift
set args=%1 %2 %3 %4 %5 %6 %7 %8 %9
set clang=clang-cl
call :CompilerVersion version
echo %clang% %version%: %std% %unit_select% %args%
set unit_config=^
-Dlest_FEATURE_AUTO_REGISTER=1 ^
-D%unit%_%UCAP%_HEADER=\"nonstd/%unit%.hpp\" ^
-D%unit%_TEST_NODISCARD=0 ^
-D%unit%_CONFIG_SELECT_%UCAP%=%unit_select%
rem -flto / -fwhole-program
set optflags=-O2
set warnflags=-Wall -Wextra -Wpedantic -Weverything -Wshadow -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-missing-noreturn -Wno-documentation-unknown-command -Wno-documentation-deprecated-sync -Wno-documentation -Wno-weak-vtables -Wno-missing-prototypes -Wno-missing-variable-declarations -Wno-exit-time-destructors -Wno-global-constructors -Wno-sign-conversion -Wno-sign-compare -Wno-implicit-int-conversion -Wno-deprecated-declarations -Wno-date-time
"%clang%" -EHsc -std:%std% %optflags% %warnflags% %unit_config% -fms-compatibility-version=19.00 /imsvc lest -I../include -Ics_string -I. -o %unit_file%-main.t.exe %unit_file%-main.t.cpp %unit_file%.t.cpp && %unit_file%-main.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> > %tmpsource%
echo int main(){printf("%%d.%%d.%%d\n",__clang_major__,__clang_minor__,__clang_patchlevel__);} >> %tmpsource%
"%clang%" -m32 -o %tmpprogram% %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,53 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: tc.bat - compile & run tests (clang).
::
set unit=expected
:: if no std is given, use c++14
set std=%1
if "%std%"=="" set std=c++14
set clang=clang
call :CompilerVersion version
echo %clang% %version%: %std%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=
rem -flto / -fwhole-program
set optflags=-O2 -fno-exceptions
set warnflags=-Wall -Wextra -Wpedantic -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-missing-noreturn -Wno-documentation-unknown-command -Wno-documentation-deprecated-sync -Wno-documentation -Wno-weak-vtables -Wno-missing-prototypes -Wno-missing-variable-declarations -Wno-exit-time-destructors -Wno-global-constructors
"%clang%" -m32 -std=%std% %optflags% %warnflags% %unit_select% %unit_config% -Dlest_FEATURE_AUTO_REGISTER=1 -fms-compatibility-version=19.00 -isystem "%VCInstallDir%include" -isystem "%WindowsSdkDir_71A%include" -I../include -I. -o %unit%-main.t.exe %unit%-noexcept.t.cpp && %unit%-noexcept.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> > %tmpsource%
echo int main(){printf("%%d.%%d.%%d\n",__clang_major__,__clang_minor__,__clang_patchlevel__);} >> %tmpsource%
"%clang%" -m32 -o %tmpprogram% %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,53 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: tc.bat - compile & run tests (clang).
::
set unit=expected
:: if no std is given, use c++14
set std=%1
if "%std%"=="" set std=c++14
set clang=clang
call :CompilerVersion version
echo %clang% %version%: %std%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=
rem -flto / -fwhole-program
set optflags=-O2
set warnflags=-Wall -Wextra -Wpedantic -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-missing-noreturn -Wno-documentation-unknown-command -Wno-documentation-deprecated-sync -Wno-documentation -Wno-weak-vtables -Wno-missing-prototypes -Wno-missing-variable-declarations -Wno-exit-time-destructors -Wno-global-constructors
"%clang%" -m32 -std=%std% %optflags% %warnflags% %unit_select% %unit_config% -Dlest_FEATURE_AUTO_REGISTER=1 -fms-compatibility-version=19.00 -isystem "%VCInstallDir%include" -isystem "%WindowsSdkDir_71A%include" -isystem lest -I../include -I. -o %unit%-main.t.exe %unit%-main.t.cpp %unit%.t.cpp && %unit%-main.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> > %tmpsource%
echo int main(){printf("%%d.%%d.%%d\n",__clang_major__,__clang_minor__,__clang_patchlevel__);} >> %tmpsource%
"%clang%" -m32 -o %tmpprogram% %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,3 @@
@for %%s in ( c++98 c++03 c++11 c++14 c++17 ) do (
call tg.bat %%s
)

View File

@@ -0,0 +1,55 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: tg.bat - compile & run tests (GNUC).
::
set unit=expected
:: if no std is given, use c++11
set std=%1
set args=%2 %3 %4 %5 %6 %7 %8 %9
if "%1" == "" set std=c++11
set gpp=g++
call :CompilerVersion version
echo %gpp% %version%: %std% %args%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=-Dnsel_CONFIG_NO_EXCEPTIONS=1
rem -flto / -fwhole-program
set optflags=-O2 -fno-exceptions
set warnflags=-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wno-padded -Wno-missing-noreturn
%gpp% -std=%std% %optflags% %warnflags% %unit_select% %unit_config% -o %unit%-main.t.exe -Dlest_FEATURE_AUTO_REGISTER=1 -I../include -I. %unit%-noexcept.t.cpp && %unit%-main.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> > %tmpsource%
echo int main(){printf("%%d.%%d.%%d\n",__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__);} >> %tmpsource%
%gpp% -o %tmpprogram% %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,55 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: tg.bat - compile & run tests (GNUC).
::
set unit=expected
:: if no std is given, use c++11
set std=%1
set args=%2 %3 %4 %5 %6 %7 %8 %9
if "%1" == "" set std=c++11
set gpp=g++
call :CompilerVersion version
echo %gpp% %version%: %std% %args%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=
rem -flto / -fwhole-program
set optflags=-O2
set warnflags=-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wno-padded -Wno-missing-noreturn
%gpp% -std=%std% %optflags% %warnflags% %unit_select% %unit_config% -o %unit%-main.t.exe -Dlest_FEATURE_AUTO_REGISTER=1 -isystem lest -I../include -I. %unit%-main.t.cpp %unit%.t.cpp && %unit%-main.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> > %tmpsource%
echo int main(){printf("%%d.%%d.%%d\n",__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__);} >> %tmpsource%
%gpp% -o %tmpprogram% %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

12
cmake/external/glib/cppgir/format.sh vendored Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
cd $1
echo -n "Running dos2unix "
git ls-files | grep '\.cpp$\|\.h$\|\.hpp$' | \
xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'"
echo
echo -n "Running clang-format "
git ls-files | grep '\.cpp$\|\.h$\|\.hpp$' | \
xargs -I {} sh -c "clang-format -i {}; echo -n '.'"
echo

503
cmake/external/glib/cppgir/gi/base.hpp vendored Normal file
View File

@@ -0,0 +1,503 @@
#ifndef GI_BASE_HPP
#define GI_BASE_HPP
#include "gi_inc.hpp"
#include "boxed.hpp"
#include "objectbase.hpp"
// un-inline some glib parts
// (otherwise they have internal linkage and not usable in non-TU-local context)
#ifdef GI_MODULE_IN_INTERFACE
#ifdef g_strdup
#undef g_strdup
#endif
#endif
GI_MODULE_EXPORT
namespace gi
{
namespace detail
{
inline std::string
exception_desc(const std::exception &e)
{
auto desc = e.what();
return desc ? desc : typeid(e).name();
}
inline std::string
exception_desc(...)
{
return "[unknown]";
}
template<typename E>
[[noreturn]] inline void
try_throw(E &&e)
{
#if GI_CONFIG_EXCEPTIONS
throw std::forward<E>(e);
#else
g_critical("no throw exception; %s", exception_desc(e).c_str());
abort();
#endif
}
// constructor does not appreciate NULL, so wrap that here
// map NULL to empty string; not quite the same, but it will do
inline std::string
make_string(const char *s)
{
return std::string(s ? s : "");
}
// helper string subtype
// used to overload unwrap of optional string argument
// (transfrom empty string to null)
// NOTE std::optional requires C++17
class optional_string : public std::string
{};
class noncopyable
{
public:
noncopyable() {}
noncopyable(const noncopyable &) = delete;
noncopyable &operator=(const noncopyable &) = delete;
noncopyable(noncopyable &&) = default;
noncopyable &operator=(noncopyable &&) = default;
};
class scope_guard : public noncopyable
{
private:
std::function<void()> cleanup_;
public:
scope_guard(std::function<void()> &&cleanup) : cleanup_(std::move(cleanup)) {}
~scope_guard() noexcept(false)
{
#if GI_CONFIG_EXCEPTIONS
#if __cplusplus >= 201703L
auto pending = std::uncaught_exceptions();
#else
auto pending = std::uncaught_exception();
#endif
try {
#endif
cleanup_();
#if GI_CONFIG_EXCEPTIONS
} catch (...) {
if (!pending)
throw;
}
#endif
}
};
// as in
// http://ericniebler.com/2013/08/07/universal-references-and-the-copy-constructo/
template<typename A, typename B>
using disable_if_same_or_derived = typename std::enable_if<
!std::is_base_of<A, typename std::remove_reference<B>::type>::value>::type;
} // namespace detail
namespace repository
{
// class types declare c type within class
// others can do so using this (e.g. enum)
template<typename CppType>
struct declare_ctype_of
{};
// and for all cases the reverse cpp type
template<typename CType>
struct declare_cpptype_of
{};
// generate code must specialize appropriately
template<typename T>
struct is_enumeration : public std::false_type
{};
template<typename T>
struct is_bitfield : public std::false_type
{};
} // namespace repository
struct transfer_full_t;
struct transfer_none_t;
namespace traits
{
template<typename T, typename U = void>
struct if_valid_type
{
typedef U type;
};
template<typename, typename = void>
struct is_type_complete : public std::false_type
{};
template<typename T>
struct is_type_complete<T, typename if_valid_type<decltype(sizeof(
typename std::decay<T>::type))>::type>
: public std::true_type
{};
template<typename T>
using is_decayed = std::is_same<typename std::decay<T>::type, T>;
template<typename T>
using is_cboxed =
typename std::conditional<std::is_base_of<detail::CBoxed, T>::value,
std::true_type, std::false_type>::type;
template<typename T>
using is_gboxed =
typename std::conditional<std::is_base_of<detail::GBoxed, T>::value,
std::true_type, std::false_type>::type;
template<typename T>
using is_boxed =
typename std::conditional<std::is_base_of<detail::Boxed, T>::value,
std::true_type, std::false_type>::type;
// avoid derived cases
template<typename T>
using is_object =
typename std::conditional<std::is_base_of<detail::ObjectBase, T>::value &&
sizeof(T) == sizeof(gpointer),
std::true_type, std::false_type>::type;
template<typename T>
using is_wrapper =
typename std::conditional<std::is_base_of<detail::wrapper_tag, T>::value &&
sizeof(T) == sizeof(gpointer),
std::true_type, std::false_type>::type;
// bring in to this namespace
using repository::is_bitfield;
using repository::is_enumeration;
// aka passthrough
template<typename T>
using is_basic =
typename std::conditional<std::is_same<T, gpointer>::value ||
std::is_same<T, gconstpointer>::value ||
std::is_arithmetic<T>::value,
std::true_type, std::false_type>::type;
// almost passthrough (on lower level at least)
template<typename T>
using is_plain = typename std::conditional<traits::is_basic<T>::value ||
std::is_enum<T>::value,
std::true_type, std::false_type>::type;
template<typename T, typename E = void>
struct is_reftype : public std::false_type
{};
template<typename T>
struct is_reftype<T,
typename if_valid_type<typename std::decay<T>::type::BoxType>::type>
: public std::true_type
{};
template<typename T, typename Enable = void>
struct has_ctype_member : public std::false_type
{};
template<typename T>
struct has_ctype_member<T,
typename if_valid_type<typename T::BaseObjectType>::type>
: public std::true_type
{};
// return corresponding c type (if any)
// (string and basic type not considered)
// preserve const
template<typename T, typename Enable = void>
struct ctype
{};
// class case
template<typename T>
struct ctype<T,
typename if_valid_type<typename std::decay<T>::type::BaseObjectType>::type>
{
typedef typename std::remove_reference<T>::type CppType;
// make sure; avoid subclassed cases
static_assert(is_wrapper<CppType>::value || is_boxed<CppType>::value,
"must be object or boxed wrapper");
typedef typename CppType::BaseObjectType CType;
typedef typename std::conditional<std::is_const<CppType>::value, const CType,
CType>::type *type;
};
// remaining cases
template<typename T>
struct ctype<T, typename if_valid_type<
typename repository::declare_ctype_of<T>::type>::type>
{
typedef typename repository::declare_ctype_of<T>::type CType;
typedef typename std::conditional<std::is_const<T>::value, const CType,
CType>::type type;
};
// basic cases passthrough
template<typename T>
struct ctype<T,
typename std::enable_if<(std::is_fundamental<T>::value &&
!std::is_same<T, bool>::value) ||
std::is_same<T, gpointer>::value ||
std::is_same<T, gconstpointer>::value>::type>
{
typedef T type;
};
// ... exception though for bool
template<>
struct ctype<bool, void>
{
typedef gboolean type;
};
// as used in callback signatures
// or in list (un)wrapping
template<>
struct ctype<const std::string &, void>
{
typedef const char *type;
};
template<>
struct ctype<std::string, void>
{
typedef char *type;
};
template<typename T1, typename T2>
struct ctype<std::pair<T1, T2>>
{
typedef std::pair<typename ctype<T1>::type, typename ctype<T2>::type> type;
};
// conversely
// return corresponding cpp type (if known)
// (string and basic type not considered)
// preserve const
template<typename T, typename Transfer = transfer_full_t,
typename Enable = void>
struct cpptype
{};
// generic
template<typename T>
struct cpptype<T *, transfer_full_t,
typename if_valid_type<typename repository::declare_cpptype_of<
typename std::remove_const<T>::type>::type>::type>
{
typedef typename repository::declare_cpptype_of<
typename std::remove_const<T>::type>::type CppType;
typedef typename std::conditional<std::is_const<T>::value, const CppType,
CppType>::type type;
};
template<typename T>
struct cpptype<T, transfer_full_t,
typename if_valid_type<typename repository::declare_cpptype_of<
typename std::remove_const<T>::type>::type>::type>
{
typedef typename repository::declare_cpptype_of<
typename std::remove_const<T>::type>::type CppType;
typedef typename std::conditional<std::is_const<T>::value, const CppType,
CppType>::type type;
};
// basic cases passthrough
template<typename T>
struct cpptype<T, transfer_full_t,
typename std::enable_if<std::is_fundamental<T>::value ||
std::is_same<T, gpointer>::value ||
std::is_same<T, gconstpointer>::value>::type>
{
typedef T type;
};
#if 0
template<>
struct cpptype<char *, transfer_full_t>
{
using type = std::string;
};
#endif
// handle none transfer case
template<typename T>
struct cpptype<T, transfer_none_t>
{
using CppType = typename cpptype<T, transfer_full_t>::type;
template<typename TT, typename Enable = void>
struct map_type
{
using type = TT;
};
template<typename TT>
struct map_type<TT, typename if_valid_type<typename TT::ReferenceType>::type>
{
using type = typename TT::ReferenceType;
};
using type = typename map_type<CppType>::type;
};
// map owning box type to corresponding reference box type
template<typename T>
struct reftype
{
typedef typename T::ReferenceType type;
};
} // namespace traits
// specify transfer type when (un)wrapping
// this approach is safer than some booleans and allows overload combinations
struct transfer_t
{
const int value;
constexpr explicit transfer_t(int v = 0) : value(v) {}
};
struct transfer_none_t : public transfer_t
{
constexpr transfer_none_t() : transfer_t(0) {}
};
struct transfer_full_t : public transfer_t
{
constexpr transfer_full_t() : transfer_t(1) {}
};
struct transfer_container_t : public transfer_t
{
constexpr transfer_container_t() : transfer_t(2) {}
};
GI_MODULE_INLINE const constexpr transfer_t transfer_dummy = transfer_t();
GI_MODULE_INLINE const constexpr transfer_none_t transfer_none =
transfer_none_t();
GI_MODULE_INLINE const constexpr transfer_full_t transfer_full =
transfer_full_t();
GI_MODULE_INLINE const constexpr transfer_container_t transfer_container =
transfer_container_t();
template<typename Transfer>
struct element_transfer
{};
template<>
struct element_transfer<transfer_none_t>
{
typedef transfer_none_t type;
};
template<>
struct element_transfer<transfer_full_t>
{
typedef transfer_full_t type;
};
template<>
struct element_transfer<transfer_container_t>
{
typedef transfer_none_t type;
};
// unwrapping a callback
// specify call scope type
struct scope_t
{
const int value;
constexpr explicit scope_t(int v = 0) : value(v) {}
};
struct scope_call_t : public scope_t
{
constexpr scope_call_t() : scope_t(0) {}
};
struct scope_async_t : public scope_t
{
constexpr scope_async_t() : scope_t(1) {}
};
struct scope_notified_t : public scope_t
{
constexpr scope_notified_t() : scope_t(2) {}
};
GI_MODULE_INLINE const constexpr scope_t scope_dummy = scope_t();
GI_MODULE_INLINE const constexpr scope_call_t scope_call = scope_call_t();
GI_MODULE_INLINE const constexpr scope_async_t scope_async = scope_async_t();
GI_MODULE_INLINE const constexpr scope_notified_t scope_notified =
scope_notified_t();
// (dummy) helper tag to aid in overload resolution
template<typename Interface>
struct interface_tag
{
typedef Interface type;
};
// CallArgs minimal helper type
// only provide what is needed
// not intended for general use, even though not in internal namespace
template<typename T>
class required
{
T data_;
public:
constexpr required(T v) : data_(std::move(v)) {}
constexpr operator T &() &noexcept { return data_; }
constexpr operator T &&() &&noexcept { return std::move(data_); }
constexpr T &value() &noexcept { return data_; }
constexpr T &&value() &&noexcept { return std::move(data_); }
};
// helper tag type to aid in overload resolution of CallArgs
// this is used as the first argument of a (non-plain) signature variant
// (since it may otherwise have only 1 struct argument, like the plain variant)
template<typename... CA_TAG>
using ca = std::tuple<CA_TAG...>;
struct ca_tag
{};
struct ca_in_tag : public ca_tag
{};
struct ca_bc_tag : public ca_tag
{};
// convenience abbreviation
using ca_in = ca<ca_in_tag>;
#if GI_DL
namespace detail
{
// dynamic load of symbol
inline void *
load_symbol(const std::vector<const char *> libs, const char *symbol)
{
void *s = nullptr;
for (const auto &l : libs) {
auto h = dlopen(l, RTLD_LAZY | RTLD_GLOBAL | RTLD_NODELETE);
if (h) {
s = dlsym(h, symbol);
dlclose(h);
if (s)
break;
}
}
return s;
}
} // namespace detail
#endif // GI_DL
} // namespace gi
#endif // GI_BASE_HPP

349
cmake/external/glib/cppgir/gi/boxed.hpp vendored Normal file
View File

@@ -0,0 +1,349 @@
#ifndef GI_BOXED_HPP
#define GI_BOXED_HPP
#include "gi_inc.hpp"
GI_MODULE_EXPORT
namespace gi
{
namespace repository
{
// specialize to enable copyable boxed wrapper
// ideally only used in case where the copy is (equivalently);
// * cheap
// * does not change the underlying pointer
// * refcount based
// * has no semantics effects
// (e.g. GstMiniObject refcount affects writable)
template<typename CType>
struct enable_boxed_copy :
#if GI_ENABLE_BOXED_COPY_ALL
public std::true_type
#else
public std::false_type
#endif
{};
} // namespace repository
namespace detail
{
// class tags
class Boxed
{};
class CBoxed : public Boxed
{};
class GBoxed : public Boxed
{};
template<typename CType>
class SharedWrapper
{
public:
typedef SharedWrapper self;
protected:
CType *data_ = nullptr;
public:
CType *gobj_() { return data_; }
const CType *gobj_() const { return data_; }
explicit operator bool() const { return (bool)data_; }
bool operator==(const SharedWrapper &other) const
{
return data_ == other.data_;
}
bool operator==(std::nullptr_t o) const { return data_ == o; }
bool operator!=(const SharedWrapper &other) const
{
return data_ != other.data_;
}
bool operator!=(std::nullptr_t o) const { return data_ != o; }
CType *release_()
{
auto tmp = this->data_;
this->data_ = nullptr;
return tmp;
}
};
template<typename CppType, typename CType>
struct GBoxedFuncs
{
static CType *copy(const void *data)
{
return (CType *)g_boxed_copy(CppType::get_type_(), data);
}
static void free(void *data) { g_boxed_free(CppType::get_type_(), data); }
};
struct CBoxedFuncsBase
{
static void free(void *data) { g_free(data); }
};
template<typename CppType, typename CType>
struct CBoxedFuncs : CBoxedFuncsBase
{
static CType *copy(const void *data)
{
#if GLIB_CHECK_VERSION(2, 68, 0)
return (CType *)g_memdup2(data, sizeof(CType));
#else
return (CType *)g_memdup(data, sizeof(CType));
#endif
}
};
template<typename CType, typename Funcs, typename TagType>
class BoxedWrapper : public SharedWrapper<CType>, public TagType
{
typedef BoxedWrapper self;
protected:
static void _deleter(CType *obj, ...)
{
if (obj)
Funcs::free(obj);
}
static CType *_copy(const CType *obj)
{
return obj ? Funcs::copy(obj) : nullptr;
}
public:
typedef CType BaseObjectType;
BoxedWrapper(CType *obj = nullptr) noexcept { this->data_ = obj; }
CType *gobj_copy_() const { return _copy(this->gobj_()); }
// resulting casted type determines ownership
template<typename Cpp>
static Cpp wrap(const typename Cpp::BaseObjectType *obj)
{
static_assert(sizeof(Cpp) == sizeof(self), "type wrap not supported");
static_assert(std::is_base_of<self, Cpp>::value, "type wrap not supported");
BoxedWrapper w(const_cast<typename Cpp::BaseObjectType *>(obj));
return std::move(*static_cast<Cpp *>(&w));
}
};
// in templates below, Base should be a subclass of BoxedWrapper
// so we re-use the members it provides, as well as the wrap template
// to avoid ambiguous reference to the latter
// (if BoxedWrapper were inherited from again)
// the nullptr_t constructor (indirectly) supports `= nullptr` (assignment)
template<typename CppType, typename CType>
using GBoxedWrapperBase =
BoxedWrapper<CType, GBoxedFuncs<CppType, CType>, GBoxed>;
// assuming Base has suitable members such as above BoxedWrapper
template<typename Base>
class MoveWrapper : public Base
{
typedef Base super;
public:
using super::super;
~MoveWrapper() { this->_deleter(this->data_, static_cast<Base *>(this)); }
MoveWrapper(const MoveWrapper &) = delete;
MoveWrapper &operator=(const MoveWrapper &) = delete;
MoveWrapper(MoveWrapper &&o) noexcept { *this = std::move(o); }
MoveWrapper &operator=(MoveWrapper &&o) noexcept
{
if (this != &o) {
this->_deleter(this->data_, static_cast<Base *>(this));
(Base &)(*this) = std::move(o);
o.data_ = nullptr;
}
return *this;
}
};
template<typename Base>
class CopyWrapper : public MoveWrapper<Base>
{
typedef MoveWrapper<Base> super;
public:
using super::super;
CopyWrapper(const CopyWrapper &o) noexcept : super()
{
this->data_ = this->_copy(o.data_);
}
CopyWrapper &operator=(const CopyWrapper &o) noexcept
{
if (this != &o) {
this->_deleter(this->data_, static_cast<Base *>(this));
if (sizeof(Base) > sizeof(this->data_))
(Base &)(*this) = std::move(o);
this->data_ = this->_copy(o.data_);
}
return *this;
}
CopyWrapper(CopyWrapper &&o) noexcept = default;
CopyWrapper &operator=(CopyWrapper &&o) noexcept = default;
// also accept from corresponding Reference (also based on Base)
CopyWrapper(const Base &o) noexcept : super()
{
this->data_ = this->_copy(((CopyWrapper &)o).data_);
}
CopyWrapper(Base &&o) noexcept : super()
{
(super &)(*this) = std::move((super &)o);
}
};
template<typename CType, typename Base>
using SelectWrapper =
typename std::conditional<repository::enable_boxed_copy<CType>::value,
CopyWrapper<Base>, MoveWrapper<Base>>::type;
// basis for registered boxed types
template<typename CppType, typename CType,
typename Base = GBoxedWrapperBase<CppType, CType>, typename RefType = void>
class GBoxedWrapper : public SelectWrapper<CType, Base>
{
typedef SelectWrapper<CType, Base> super;
public:
typedef RefType ReferenceType;
using super::super;
GBoxedWrapper(std::nullptr_t = nullptr) {}
void allocate_()
{
if (this->data_)
return;
// make sure we match boxed allocation with boxed free
// still guessing here that all-0 makes for a decent init :-(
CType tmp;
memset(&tmp, 0, sizeof(tmp));
this->data_ = (CType *)g_boxed_copy(this->get_type_(), &tmp);
}
// essentially g_boxed_copy
CppType copy_() const
{
return super::template wrap<CppType>(this->gobj_copy_());
}
};
template<typename CppType, typename CType>
using CBoxedWrapperBase =
BoxedWrapper<CType, CBoxedFuncs<CppType, CType>, CBoxed>;
// basis for non-registered plain C boxed type
template<typename CppType, typename CType,
typename Base = CBoxedWrapperBase<CppType, CType>, typename RefType = void>
class CBoxedWrapper : public MoveWrapper<Base>
{
typedef Base super;
public:
typedef RefType ReferenceType;
CBoxedWrapper(std::nullptr_t = nullptr) {}
void allocate_()
{
if (this->data_)
return;
this->data_ = g_new0(CType, 1);
}
static CppType new_()
{
return super::template wrap<CppType>(g_new0(CType, 1));
}
};
// allocate helper;
// dispatch to method if available
template<typename T, typename Enable = void>
struct allocator : public std::false_type
{
static void allocate(T &) {}
};
template<typename T>
struct allocator<T, decltype(T().allocate_())> : public std::true_type
{
static void allocate(T &v) { v.allocate_(); }
};
template<typename T>
void
allocate(T &v)
{
allocator<T>::allocate(v);
}
// basis for ref-holding to registered box type
template<typename CppType, typename CType, typename Base>
class GBoxedRefWrapper : public Base
{
typedef Base super;
public:
typedef CppType BoxType;
GBoxedRefWrapper(std::nullptr_t = nullptr) {}
// construct from owning version, but not the other way around
// (which requires an explicit copy)
GBoxedRefWrapper(const CppType &other)
{
(super &)(*this) = super::template wrap<super>(other.gobj_());
}
// support way to convert to owning box
// (by means of copy as opposed to a simple ref)
CppType copy_() const
{
return super::template wrap<CppType>(this->gobj_copy_());
}
};
// basis for ref-holding to non-registered plain C boxed type
template<typename CppType, typename CType, typename Base>
class CBoxedRefWrapper : public Base
{
typedef Base super;
public:
typedef CppType BoxType;
CBoxedRefWrapper(std::nullptr_t = nullptr) {}
// construct from owning version, but not the other way around
// (which requires an explicit copy)
CBoxedRefWrapper(const CppType &other)
{
(super &)(*this) = super::template wrap<super>(other.gobj_());
}
};
} // namespace detail
} // namespace gi
#endif // GI_BOXED_HPP

View File

@@ -0,0 +1,960 @@
#ifndef GI_CALLBACK_HPP
#define GI_CALLBACK_HPP
#include "base.hpp"
#include "exception.hpp"
#include "wrap.hpp"
GI_MODULE_EXPORT
namespace gi
{
namespace detail
{
#if GI_CONFIG_EXCEPTIONS
inline ::GError **
find_gerror(bool &has_gerror)
{
has_gerror = false;
return nullptr;
}
inline ::GError **
find_gerror(bool &has_gerror, ::GError **error)
{
has_gerror = true;
return error;
}
template<typename CT, typename... CType>
inline ::GError **
find_gerror(bool &has_gerror, CT /*arg*/, CType... args)
{
return find_gerror(has_gerror, args...);
}
inline ::GError *
exception_error(const repository::GLib::Error &exc)
{
return g_error_copy(exc.gobj_());
}
inline ::GError *
exception_error(const repository::GLib::Error_Ref &exc)
{
return g_error_copy(exc.gobj_());
}
template<typename E>
inline ::GError *
exception_error(const E &exc)
{
static auto quark = g_quark_from_static_string("gi-error-quark");
return g_error_new(quark, 0, "%s", exception_desc(exc).c_str());
}
template<bool SILENT = FALSE, typename E, typename... CType>
void
report_exception(const E &exc, CType... args)
{
// see if we can really report error somewhere
bool has_gerror = false;
GError **error = find_gerror(has_gerror, args...);
if (has_gerror) {
g_return_if_fail(error == NULL || *error == NULL);
if (error)
*error = exception_error(exc);
// if caller does not need/want error, exception disappears here
} else {
// simply report the hard and simple way
// otherwise catch internally if something else/more is desired
if (!SILENT) {
auto msg = std::string("handler exception; ") + exception_desc(exc);
g_critical("%s", msg.c_str());
}
}
}
#endif
// (re)float only applies to GObject
template<typename CppType, typename Transfer,
typename std::enable_if<!traits::is_object<CppType>::value>::type * =
nullptr>
auto
unwrap_maybe_float(CppType &&v, const Transfer &t)
{
return unwrap(std::forward<CppType>(v), t);
}
// (re) float only for transfer none/floating
template<typename CppType,
typename std::enable_if<traits::is_object<CppType>::value>::type * =
nullptr>
inline typename traits::ctype<CppType>::type
unwrap_maybe_float(CppType &&v, const transfer_full_t &t)
{
return unwrap(std::forward<CppType>(v), t);
}
template<typename CppType,
typename std::enable_if<traits::is_object<CppType>::value>::type * =
nullptr>
inline typename traits::ctype<CppType>::type
unwrap_maybe_float(CppType &&v, const transfer_none_t &)
{
// expected called with r-value
static_assert(!std::is_reference<CppType>::value, "");
// steal/take from wrapper
auto result = (typename traits::ctype<CppType>::type)v.release_();
// the following is essentially a bit of a hack as mentioned in
// https://bugzilla.gnome.org/show_bug.cgi?id=693393)
// that is, we are about to return an object to C (from binding/callback)
// and this should be done with none transfer
// this none may actually mean floating (e.g. a factory-like callback)
// but no way to know from annotations
// so if at runtime the wrapper actually holds the only reference,
// then it is about to be destroyed (when wrapper goes away)
// *before* the object can make it back to caller
// so turn that ref into a floating one (as only that makes sense)
// if it is not the only ref, it is kept alive elsewhere
// (as typically so for a "getter" callback)
// so it is really treated as none
auto obj = (GObject *)result;
// theoretically not MT safe, but if == 1, only 1 thread should be involved
if (obj->ref_count == 1) {
g_object_force_floating(obj);
} else {
// otherwise unref as wrapper would have
g_object_unref(obj);
}
return result;
}
template<typename T>
constexpr T
unconst(T t)
{
return t;
}
inline char *
unconst(const char *t)
{
return (char *)t;
}
// helper types to provide additional argument info beyond Transfer
template<std::size_t... index>
struct args_index
{
static constexpr auto value = std::make_tuple(index...);
using value_type = decltype(value);
};
template<typename Transfer, bool _inout, typename CustomTraits = void,
typename ArgsIndex = args_index<>>
struct arg_info
{
using transfer_type = Transfer;
static constexpr bool inout = _inout;
// tuple of index; used to selects the C arguments (to assemble C++ argument)
using args_type = ArgsIndex;
// additional info as used by corresponding cb_arg_handler
using custom_type = CustomTraits;
};
// access above info by forwarding types
template<typename T, typename Enable = void>
struct arg_traits
{
using transfer_type = typename T::transfer_type;
static constexpr bool inout = T::inout;
using args_type = typename T::args_type;
using custom_type = typename T::custom_type;
};
// legacy case; only transfer type
template<typename T>
struct arg_traits<T,
typename std::enable_if<std::is_base_of<transfer_t, T>::value>::type>
{
using transfer_type = T;
static constexpr bool inout = false;
using args_type = args_index<>;
using custom_type = void;
};
// IndexTuple is essentially an args_index<...>
template<typename IndexTuple, std::size_t... Index, typename F,
typename ArgTuple>
decltype(auto)
apply_with_args(std::index_sequence<Index...>, F &&f, ArgTuple &&args)
{
return f(std::get<std::get<Index>(IndexTuple::value)>(args)...);
}
template<typename IndexTuple, typename F, typename... Args>
decltype(auto)
apply_with_args(F &&f, Args &&...args)
{
return apply_with_args<IndexTuple>(
std::make_index_sequence<
std::tuple_size<typename IndexTuple::value_type>::value>(),
std::forward<F>(f), std::forward_as_tuple(args...));
}
// a simple callback has no (need for) args_index
template<typename T>
struct is_simple_cb : public std::true_type
{};
template<typename Transfer, typename... Transfers>
struct is_simple_cb<std::tuple<Transfer, Transfers...>>
{
static constexpr bool value =
std::tuple_size<
typename arg_traits<Transfer>::args_type::value_type>::value == 0 ||
(sizeof...(Transfers) > 0 &&
is_simple_cb<std::tuple<Transfers...>>::value);
};
template<typename T, typename CT = void>
struct map_cpp_function;
// handles all calls C -> C++ (callbacks, virtual method calls)
// restrictions though on types supported (enforced by code generation)
template<typename T, typename RetTransfer, typename ArgTransfers,
typename CT = typename map_cpp_function<T>::type,
bool SIMPLE = is_simple_cb<ArgTransfers>::value>
struct transform_caller;
// helper used below that provides pre-call and post-call steps
// to handle each argument's conversion to and from C++
// in so-called (most) simple cases, there is one-to-one mapping between
// C and C++ arguments and C++ argument type that allows to deduce context
// (in particular, no callbacks or sized array)
// as such, proper steps can be obtained by specialization on Cpp argument type
template<typename CppArg, typename Enable = void>
struct cb_arg_handler;
// in simple cases, C signature can be derived from C++ signature
template<typename T, typename CT>
struct map_cpp_function
{
using type = CT;
};
template<typename R, typename... Args>
struct map_cpp_function<R(Args...), void>
{
using type = typename traits::ctype<R>::type(
typename cb_arg_handler<Args>::c_type...);
};
// signature used in virtual method handling
template<typename R, typename... Args>
struct map_cpp_function<R (*)(Args...), void>
{
using type = typename traits::ctype<R>::type(
typename cb_arg_handler<Args>::c_type...);
};
// NOTE function type R(const A) is identical to R(A)
// so no deduced Args below will retain const (if such)
// in simple cases, *Transfer* is simply a transfer type
// but it may also be a more elaborate argument trait
template<typename R, typename... Args, typename RetTransfer,
typename... Transfers, typename CR, typename... CArgs, bool SIMPLE>
struct transform_caller<R(Args...), RetTransfer, std::tuple<Transfers...>,
CR(CArgs...), SIMPLE>
{
static_assert(sizeof...(Args) == sizeof...(Transfers), "");
static_assert(!SIMPLE || sizeof...(Args) == sizeof...(CArgs), "");
typedef transform_caller self_type;
typedef R (*caller_type)(Args &&..., void *d);
private:
static R do_call(Args &&...args, caller_type func, void *d)
{
return func(std::forward<Args>(args)..., d);
}
// helper that provides context for pack expansion below
static void dummy_call(...){};
template<typename T>
static auto _tt(const T &)
{
return typename arg_traits<T>::transfer_type();
}
// non-void return
template<typename T, std::size_t... Index,
typename std::enable_if<SIMPLE && !std::is_void<T>::value>::type * =
nullptr>
static CR _wrapper(
CArgs... args, caller_type func, void *d, std::index_sequence<Index...>)
{
std::tuple<cb_arg_handler<Args>...> handlers;
auto ret = do_call(
std::get<Index>(handlers).arg(args, _tt(Transfers()), Transfers())...,
func, d);
dummy_call((std::get<Index>(handlers).post(args, _tt(Transfers())), 0)...);
return unconst(unwrap_maybe_float(std::move(ret), RetTransfer()));
}
// void return
template<typename T, std::size_t... Index,
typename std::enable_if<SIMPLE && std::is_void<T>::value>::type * =
nullptr>
static CR _wrapper(
CArgs... args, caller_type func, void *d, std::index_sequence<Index...>)
{
std::tuple<cb_arg_handler<Args>...> handlers;
do_call(
std::get<Index>(handlers).arg(args, _tt(Transfers()), Transfers())...,
func, d);
dummy_call((std::get<Index>(handlers).post(args, _tt(Transfers())), 0)...);
}
// complex; non-void return
template<typename T, std::size_t... Index,
typename std::enable_if<!SIMPLE && !std::is_void<T>::value>::type * =
nullptr>
static CR _wrapper(
CArgs... args, caller_type func, void *d, std::index_sequence<Index...>)
{
std::tuple<cb_arg_handler<Args>...> handlers;
auto ret = do_call(
apply_with_args<typename arg_traits<Transfers>::args_type>(
[&handlers](auto... selargs) mutable -> decltype(auto) {
return std::get<Index>(handlers).arg(selargs...,
typename arg_traits<Transfers>::transfer_type(), Transfers());
},
args...)...,
func, d);
dummy_call((apply_with_args<typename arg_traits<Transfers>::args_type>(
[&handlers](auto... selargs) mutable -> decltype(auto) {
return std::get<Index>(handlers).post(selargs...,
typename arg_traits<Transfers>::transfer_type());
},
args...),
0)...);
return unconst(unwrap_maybe_float(std::move(ret), RetTransfer()));
}
// complex; void return
template<typename T, std::size_t... Index,
typename std::enable_if<!SIMPLE && std::is_void<T>::value>::type * =
nullptr>
static CR _wrapper(
CArgs... args, caller_type func, void *d, std::index_sequence<Index...>)
{
std::tuple<cb_arg_handler<Args>...> handlers;
do_call(apply_with_args<typename arg_traits<Transfers>::args_type>(
[&handlers](auto... selargs) mutable -> decltype(auto) {
return std::get<Index>(handlers).arg(selargs...,
typename arg_traits<Transfers>::transfer_type(),
Transfers());
},
args...)...,
func, d);
dummy_call((apply_with_args<typename arg_traits<Transfers>::args_type>(
[&handlers](auto... selargs) mutable -> decltype(auto) {
return std::get<Index>(handlers).post(selargs...,
typename arg_traits<Transfers>::transfer_type());
},
args...),
0)...);
}
public:
static CR wrapper(CArgs... args, caller_type func, void *d)
{
// exceptions should not escape into plain C
#if GI_CONFIG_EXCEPTIONS
try {
#endif
return self_type::template _wrapper<R>(
args..., func, d, std::make_index_sequence<sizeof...(Args)>());
#if GI_CONFIG_EXCEPTIONS
} catch (const repository::GLib::Error &exc) {
report_exception(exc, args...);
} catch (const repository::GLib::Error_Ref &exc) {
report_exception(exc, args...);
} catch (const std::exception &exc) {
report_exception(exc, args...);
} catch (...) {
report_exception(nullptr, args...);
}
return typename traits::ctype<R>::type();
#endif
}
};
// minor helper traits used below
namespace _traits
{
template<typename T>
struct remove_all_pointers
{
using type = T;
};
template<typename T>
struct remove_all_pointers<T *>
{
using type = typename remove_all_pointers<T>::type;
};
template<typename T>
struct remove_all_pointers<T *const>
{
using type = typename remove_all_pointers<T>::type;
};
template<typename T>
using is_basic_or_void = typename std::conditional<traits::is_basic<T>::value ||
std::is_void<T>::value,
std::true_type, std::false_type>::type;
template<typename T>
using is_basic_argument = typename std::conditional<
is_basic_or_void<typename std::remove_cv<
typename remove_all_pointers<T>::type>::type>::value,
std::true_type, std::false_type>::type;
template<typename T>
using is_const_lvalue = typename std::conditional<
std::is_lvalue_reference<T>::value &&
std::is_const<typename std::remove_reference<T>::type>::value,
std::true_type, std::false_type>::type;
} // namespace _traits
// generic fallback case; assume input parameter
// (also covers boxed callerallocates, which pretty much is/becomes input)
template<typename CppArg, typename Enable>
struct cb_arg_handler
{
using c_type = typename traits::ctype<CppArg>::type;
// use generic CType to handle const differences wrt c_type
template<typename CType, typename Transfer, typename ArgTrait>
typename std::decay<CppArg>::type arg(
CType arg, const Transfer &t, const ArgTrait &) noexcept
{
// wrap to normalized destination target (no const &)
// the destination type here is really only relevant for collection cases
// (since that depends on the contained element)
// otherwise all the info is pretty much in c_type type
return wrap_to<typename std::decay<CppArg>::type>(arg, t);
}
// minor variation; dynamic sized array input
// also accepts length input
template<typename CType, typename Transfer, typename ArgTrait>
typename std::decay<CppArg>::type arg(
CType arg, int length, const Transfer &t, const ArgTrait &) noexcept
{
// destination also really needed here
return wrap_to<typename std::decay<CppArg>::type>(arg, length, t);
}
void post(...){};
};
// passthrough on (pointers to) basic values;
// handles input/output of basic values, as well as arrays of such
template<typename CppArg>
struct cb_arg_handler<CppArg,
typename std::enable_if<_traits::is_basic_argument<CppArg>::value>::type>
{
using c_type = CppArg;
template<typename CType, typename Transfer, typename ArgTrait>
CType arg(CType arg, const Transfer &, const ArgTrait &) noexcept
{
return arg;
}
void post(...){};
// array size case; inout C int* to in Cpp int
// (only enable if so tagged by non-default custom trait type)
template<typename CType, typename Transfer, typename ArgTrait>
typename std::enable_if<
!std::is_void<typename arg_traits<ArgTrait>::custom_type>::value,
CType>::type
arg(CType *arg, const Transfer &, const ArgTrait &) noexcept
{
return *arg;
}
};
// handle "complex" (in)out argument
// (though also handles e.g. int& case, which might be optimized, but anyways)
// these are recognized/assumed to be a pointer or reference to non-basic type
template<typename CppArg>
struct cb_arg_handler<CppArg,
typename std::enable_if<
!_traits::is_basic_argument<CppArg>::value &&
(std::is_pointer<CppArg>::value ||
(std::is_lvalue_reference<CppArg>::value &&
!_traits::is_const_lvalue<CppArg>::value))>::type>
{
using BaseCppType =
typename std::decay<typename std::remove_pointer<CppArg>::type>::type;
// no more pointer expected here
// (other than for void* cases, which do not introspect well, but anyways)
static_assert(!std::is_pointer<BaseCppType>::value ||
std::is_same<BaseCppType, gpointer>::value ||
std::is_same<BaseCppType, gconstpointer>::value,
"");
using c_type = typename traits::ctype<BaseCppType>::type *;
// intermediate helper storage
BaseCppType var_{};
// pointer case
CppArg rv(std::true_type) { return &var_; }
// ref case
CppArg rv(std::false_type) { return var_; }
// handle const variations (e.g. const char**)
template<typename T, typename X>
static void assign(T *&t, X val)
{
t = const_cast<T *>(val);
}
template<typename T, typename X>
static void assign(T &t, X val)
{
t = unconst(val);
}
template<typename Transfer, typename ArgTrait>
CppArg arg(c_type arg, const Transfer &t, const ArgTrait &)
{
bool inout = arg_traits<ArgTrait>::inout;
// a plain type has no special needs
// so we can always take any bogus value as-is
// (which is then less sensitive to incorrect annotation)
if (arg && (inout || traits::is_plain<BaseCppType>::value))
var_ = wrap_to<BaseCppType>(*arg, t);
return rv(std::is_pointer<CppArg>());
}
// overall function call happens following arg call above
// post invoked after function call
template<typename Transfer>
void post(c_type arg, const Transfer &t)
{
if (arg)
assign(*arg, unwrap_maybe_float(std::move(var_), t));
}
// sized array variants; arguments (data, size)
// latter could be input (int) or (in)out (int*)
template<typename Transfer, typename Int, typename ArgTrait>
CppArg arg(c_type arg, Int size, const Transfer &t, const ArgTrait &)
{
bool inout = arg_traits<ArgTrait>::inout;
if (arg && inout)
var_ = wrap_to<BaseCppType>(*arg, size, t);
return rv(std::is_pointer<CppArg>());
}
template<typename Transfer, typename Int, typename ArgTrait>
CppArg arg(c_type arg, Int *size, const Transfer &t, const ArgTrait &)
{
bool inout = arg_traits<ArgTrait>::inout;
if (arg && size && inout)
var_ = wrap_to<BaseCppType>(*arg, *size, t);
return rv(std::is_pointer<CppArg>());
}
template<typename Transfer, typename Int>
void post(c_type arg, Int, const Transfer &t)
{
post(arg, nullptr, t);
}
template<typename Transfer, typename Int>
void post(c_type arg, Int *size, const Transfer &t)
{
if (arg)
assign(*arg, unwrap_maybe_float(std::move(var_), t));
if (size)
*size = var_.size();
}
};
// handles callback argument
// (as argument in anther callback, most likely a virtual method)
template<typename CppArg>
struct cb_arg_handler<CppArg,
typename std::enable_if<
std::is_pointer<typename CppArg::cfunction_type>::value>::type>
{
template<typename CType, typename Transfer, typename ArgTrait>
CppArg arg(CType cb, gpointer userdata, ::GDestroyNotify destroy,
const Transfer &, const ArgTrait &) noexcept
{
using ct = typename arg_traits<ArgTrait>::custom_type;
// setup shared pointer to userdata with destroy as Deleter
auto sp = destroy ? std::shared_ptr<void>(userdata, destroy) : nullptr;
// keep userdata/destroy alive as long as lambda handler
auto h = [cb, userdata, sp = std::move(sp)](
auto &&...args) -> decltype(auto) {
// original callback type CType should match deduced handler_cb_tpe
// but let's make sure as usual
return ct::handler(std::forward<decltype(args)>(args)...,
typename ct::handler_cb_type(cb), userdata);
};
return h;
}
template<typename CType, typename Transfer, typename ArgTrait>
CppArg arg(CType cb, gpointer userdata, const Transfer &t,
const ArgTrait &tt) noexcept
{
return arg(cb, userdata, nullptr, t, tt);
}
void post(...){};
};
class connection_status
{
public:
struct data
{
bool connected{false};
};
bool connected() const
{
auto sp = data_.lock();
return sp && sp->connected;
}
protected:
std::weak_ptr<data> data_;
};
// callback handling
template<typename G>
class connectable;
template<typename G, bool AUTODESTROY = false>
class callback_wrapper;
template<typename R, typename... Args>
class connectable<R(Args...)>
{
friend class callback_wrapper<R(Args...)>;
struct data : public connection_status::data
{
template<typename T,
typename Enable = typename detail::disable_if_same_or_derived<data, T>>
data(T &&t) : callable(std::forward<T>(t))
{}
std::function<R(Args... args)> callable;
};
struct connection_status_factory : public connection_status
{
connection_status_factory(std::shared_ptr<data> p)
{
data_ = std::weak_ptr<connection_status::data>(p);
}
};
public:
// avoid copy constructor mishaps
template<typename T,
typename Enable =
typename detail::disable_if_same_or_derived<connectable, T>>
connectable(T &&t) : data_(std::make_shared<data>(std::forward<T>(t)))
{}
connection_status connection() const
{
return connection_status_factory(data_);
}
R operator()(Args... args) const
{
return data_->callable(std::forward<Args>(args)...);
}
explicit operator bool() const { return data_ && data_->callable; }
private:
// state management by wrapper
void connected(bool conn) { data_->connected = conn; }
void disconnect() { data_->connected = false; }
private:
std::shared_ptr<data> data_;
};
template<typename R, typename... Args, bool AUTODESTROY>
class callback_wrapper<R(Args...), AUTODESTROY>
{
typedef callback_wrapper self_type;
public:
template<typename T,
typename Enable =
typename detail::disable_if_same_or_derived<callback_wrapper, T>>
callback_wrapper(T &&t) : _callback(std::forward<T>(t))
{
// mark connected now that it is wrapped
_callback.connected(true);
}
// (only) used by manual callback workaround
static R wrapper(Args... args, gpointer user_data)
{
auto t = reinterpret_cast<callback_wrapper *>(user_data);
std::unique_ptr<self_type> wt(AUTODESTROY ? t : nullptr);
return t->_callback(args...);
}
static void destroy(gpointer user_data)
{
auto t = reinterpret_cast<callback_wrapper *>(user_data);
delete t;
}
// (async scope) wrapper may have to take ownership of additional data
// (other callback wrapper)
template<typename T>
void take_data(std::shared_ptr<T> d)
{
auto cb = std::move(_callback.data_->callable);
auto newcb = [d, cb](Args &&...args) {
return cb(std::forward<Args>(args)...);
};
_callback.data_->callable = std::move(newcb);
}
template<typename T>
void take_data(T *d)
{
take_data(std::shared_ptr<T>(d));
}
~callback_wrapper()
{
// other shared ptr to data might be around (unlikely though)
// but regardless disconnect now as requested (as wrapper is going away)
_callback.disconnect();
}
connectable<R(Args... args)> _callback;
};
template<typename G, typename CG = typename map_cpp_function<G>::type>
struct transform_callback_wrapper;
template<typename R, typename... Args, typename CR, typename... CArgs>
struct transform_callback_wrapper<R(Args...), CR(CArgs...)>
{
// transfers of arguments
template<bool AUTODESTROY, typename RetTransfer, typename... Transfers>
class with_transfer : public callback_wrapper<R(Args...)>
{
typedef callback_wrapper<R(Args...)> super_type;
typedef with_transfer self_type;
public:
template<typename T>
explicit with_transfer(T &&t) : super_type(std::forward<T>(t))
{}
private:
static R caller(Args &&...args, void *d)
{
auto this_ = (self_type *)d;
return this_->_callback(std::forward<Args>(args)...);
}
public:
static CR wrapper(CArgs... args, gpointer user_data)
{
auto t = reinterpret_cast<self_type *>(user_data);
std::unique_ptr<self_type> wt(AUTODESTROY ? t : nullptr);
return transform_caller<R(Args...), RetTransfer, std::tuple<Transfers...>,
CR(CArgs...)>::wrapper(args..., caller, t);
}
};
};
// used early in declarations, so avoid using unknown types in CSigOrVoid
template<typename CppSig, typename RetTransfer,
typename ArgTransfers = std::tuple<>, typename CSigOrVoid = void>
class callback;
template<typename CppSig, typename RetTransfer, typename... Transfers,
typename CSigOrVoid>
class callback<CppSig, RetTransfer, std::tuple<Transfers...>, CSigOrVoid>
: public connectable<CppSig>
{
typedef connectable<CppSig> super_type;
public:
typedef CppSig function_type;
typedef typename map_cpp_function<CppSig, CSigOrVoid>::type CSig;
template<bool ASYNC = false>
using wrapper_type = typename transform_callback_wrapper<function_type,
CSig>::template with_transfer<ASYNC, RetTransfer, Transfers...>;
typedef typename std::add_pointer<decltype(wrapper_type<>::wrapper)>::type
cfunction_type;
using super_type::super_type;
};
// signal handling;
// transfer none for arguments
// transfer full for return
template<typename G>
struct transform_signal_wrapper;
template<typename T>
struct signal_arg_transfer
{
typedef transfer_none_t type;
};
template<typename R, typename... Args>
struct transform_signal_wrapper<R(Args...)>
: public transform_callback_wrapper<R(
Args...)>::template with_transfer<false, transfer_full_t,
typename detail::signal_arg_transfer<Args>::type...>
{
private:
typedef typename transform_callback_wrapper<R(
Args...)>::template with_transfer<false, transfer_full_t,
typename detail::signal_arg_transfer<Args>::type...>
super_;
public:
template<typename T>
transform_signal_wrapper(T &&t) : super_(std::forward<T>(t))
{}
};
// connection helpers
class connection_impl
{
public:
connection_impl(gulong id, connection_status s) : id_(id), status_(s) {}
bool connected() const { return status_.connected(); }
gulong id() const { return id_; }
protected:
gulong id_;
connection_status status_;
};
template<typename Connection>
class connection
{
typedef Connection impl;
public:
connection() = default;
template<typename... Args>
explicit connection(Args... arg)
: conn_(std::make_shared<impl>(std::forward<Args>(arg)...))
{
// this is to be expected at this time
if (!connected()) {
g_warning("creating non-connected connection");
}
}
// implicit copy/move
bool connected() const { return conn_ && conn_->connected(); }
void disconnect()
{
if (connected())
conn_->disconnect();
}
protected:
std::shared_ptr<impl> conn_;
};
template<typename ConnectionBase>
class scoped_connection : public ConnectionBase
{
typedef ConnectionBase connection;
public:
scoped_connection() : connection() {}
~scoped_connection() { this->disconnect(); }
void release() { this->conn_.reset(); }
// ensure default movable
scoped_connection(scoped_connection &&other) = default;
scoped_connection &operator=(scoped_connection &&other) = default;
// not copyable; to avoid inadvertent disconnect
scoped_connection(const scoped_connection &other) = delete;
scoped_connection &operator=(const scoped_connection &other) = delete;
// but convert/assign from base class
scoped_connection(const connection &other) : connection(other) {}
scoped_connection &operator=(const connection &other)
{
(*(connection *)(this)) = other;
return *this;
}
scoped_connection &operator=(const connection &&other)
{
(*(connection *)(this)) = std::move(other);
return *this;
}
};
} // namespace detail
// function bind helpers
template<typename R, typename T, typename Tp, typename... Args>
inline std::function<R(Args...)>
mem_fun(R (T::*pm)(Args...), Tp object)
{
return [object, pm](Args... args) {
return (object->*pm)(std::forward<Args>(args)...);
};
}
template<typename R, typename T, typename Tp, typename... Args>
inline std::function<R(Args...)>
mem_fun(R (T::*pm)(Args...) const, Tp object)
{
return [object, pm](Args... args) {
return (object->*pm)(std::forward<Args>(args)...);
};
}
// expose for use in fallback scenarios
using detail::callback_wrapper;
} // namespace gi
#endif // CALLBACK_HPP

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,268 @@
#ifndef GI_ENUMFLAG_HPP
#define GI_ENUMFLAG_HPP
#include "base.hpp"
#include "exception.hpp"
#include "value.hpp"
GI_MODULE_EXPORT
namespace gi
{
namespace detail
{
struct EnumValueTraits
{
typedef GEnumClass class_type;
typedef GEnumValue value_type;
static class_type *get_class(GType gtype)
{
auto c = g_type_class_peek(gtype);
return (class_type *)(c ? G_ENUM_CLASS(c) : c);
}
static value_type *get_value(class_type *klass, int v)
{
return g_enum_get_value(klass, v);
}
static value_type *get_by_name(class_type *klass, const char *name)
{
return g_enum_get_value_by_name(klass, name);
}
static value_type *get_by_nick(class_type *klass, const char *name)
{
return g_enum_get_value_by_nick(klass, name);
}
};
struct FlagsValueTraits
{
typedef GFlagsClass class_type;
typedef GFlagsValue value_type;
static class_type *get_class(GType gtype)
{
auto c = g_type_class_peek(gtype);
return (class_type *)(c ? G_FLAGS_CLASS(c) : c);
}
static value_type *get_value(class_type *klass, int v)
{
return g_flags_get_first_value(klass, v);
}
static value_type *get_by_name(class_type *klass, const char *name)
{
return g_flags_get_value_by_name(klass, name);
}
static value_type *get_by_nick(class_type *klass, const char *name)
{
return g_flags_get_value_by_nick(klass, name);
}
};
template<typename EnumType, typename Traits>
class EnumValue
{
typedef EnumValue self;
typedef typename Traits::value_type value_type;
typedef typename Traits::class_type class_type;
value_type *value_;
static class_type *get_class()
{
auto c = Traits::get_class(get_type_());
if (!c) {
detail::try_throw(std::invalid_argument("unknown class"));
} else {
return c;
}
}
EnumValue(gint v) : EnumValue(static_cast<EnumType>(v)) {}
EnumValue(value_type *_value) : value_(_value) {}
public:
typedef EnumType enum_type;
static GType get_type_() { return traits::gtype<EnumType>::get_type(); }
EnumValue(EnumType v) : value_(Traits::get_value(get_class(), (int)v))
{
if (!value_)
detail::try_throw(std::invalid_argument(
"invalid value " + std::to_string((unsigned)v)));
}
static self get_by_name(const gi::cstring_v name)
{
auto v = Traits::get_by_name(get_class(), name.c_str());
if (!v)
detail::try_throw(std::invalid_argument("unknown name"));
else
return self(v);
}
static self get_by_nick(const gi::cstring_v nick)
{
auto v = Traits::get_by_nick(get_class(), nick.c_str());
if (!v)
detail::try_throw(std::invalid_argument("unknown nick"));
else
return self(v);
}
operator EnumType() const noexcept
{
return static_cast<EnumType>(value_->value);
}
gi::cstring_v value_name() const noexcept { return value_->value_name; }
gi::cstring_v value_nick() const noexcept { return value_->value_nick; }
};
} // namespace detail
// enumeration nick/name helpers
template<typename EnumType>
using EnumValue = detail::EnumValue<EnumType, detail::EnumValueTraits>;
template<typename EnumType,
typename std::enable_if<traits::is_enumeration<EnumType>::value &&
traits::gtype<EnumType>::value>::type * = nullptr>
inline EnumValue<EnumType>
value_info(EnumType v)
{
return EnumValue<EnumType>(v);
}
// bitfield nick/name helpers
template<typename EnumType>
using FlagsValue = detail::EnumValue<EnumType, detail::FlagsValueTraits>;
template<typename EnumType,
typename std::enable_if<traits::is_bitfield<EnumType>::value &&
traits::gtype<EnumType>::value>::type * = nullptr>
inline FlagsValue<EnumType>
value_info(EnumType v)
{
return FlagsValue<EnumType>(v);
}
// bitfield operation helpers
template<typename FlagType,
typename std::enable_if<traits::is_bitfield<FlagType>::value>::type * =
nullptr>
inline FlagType
operator|(FlagType lhs, FlagType rhs)
{
return static_cast<FlagType>(
static_cast<unsigned>(lhs) | static_cast<unsigned>(rhs));
}
template<typename FlagType,
typename std::enable_if<traits::is_bitfield<FlagType>::value>::type * =
nullptr>
inline FlagType
operator&(FlagType lhs, FlagType rhs)
{
return static_cast<FlagType>(
static_cast<unsigned>(lhs) & static_cast<unsigned>(rhs));
}
template<typename FlagType,
typename std::enable_if<traits::is_bitfield<FlagType>::value>::type * =
nullptr>
inline FlagType
operator^(FlagType lhs, FlagType rhs)
{
return static_cast<FlagType>(
static_cast<unsigned>(lhs) ^ static_cast<unsigned>(rhs));
}
template<typename FlagType,
typename std::enable_if<traits::is_bitfield<FlagType>::value>::type * =
nullptr>
inline FlagType
operator~(FlagType flags)
{
return static_cast<FlagType>(~static_cast<unsigned>(flags));
}
template<typename FlagType,
typename std::enable_if<traits::is_bitfield<FlagType>::value>::type * =
nullptr>
inline FlagType &
operator|=(FlagType &lhs, FlagType rhs)
{
return (lhs = static_cast<FlagType>(
static_cast<unsigned>(lhs) | static_cast<unsigned>(rhs)));
}
template<typename FlagType,
typename std::enable_if<traits::is_bitfield<FlagType>::value>::type * =
nullptr>
inline FlagType &
operator&=(FlagType &lhs, FlagType rhs)
{
return (lhs = static_cast<FlagType>(
static_cast<unsigned>(lhs) & static_cast<unsigned>(rhs)));
}
template<typename FlagType,
typename std::enable_if<traits::is_bitfield<FlagType>::value>::type * =
nullptr>
inline FlagType &
operator^=(FlagType &lhs, FlagType rhs)
{
return (lhs = static_cast<FlagType>(
static_cast<unsigned>(lhs) ^ static_cast<unsigned>(rhs)));
}
} // namespace gi
// sadly, the above templates are not picked up by ADL
// and might therefore only end up used in case of a `using namespace gi`
// so also explicitly add operators in generated namespace
// (by means of macro to simplify generation)
// see macro interface
/* NOTE:
*
* enumeration/bitfield TypeX is currently represented by an enum class TypeX
* along with a namespace TypeXNS_ that holds the member functions.
*
* This could be merged into single class TypeX
* (possibly using templated helpers);
*
* class TypeX
* {
* enum Enum {
* VALUE_A,
* VALUE_B
* }
* Enum value;
*
* (or C++17):
* inline static const TypeX VALUE_A;
* (note; constexpr not possible with incomplete type)
*
* constructor (Enum)
* operator Enum()
* ... etc ...
* std::string value_name(); [opt]
* std::string value_nick(); [opt]
* }
*
* Disadvantage is that TypeX::VALUE_A would not have type TypeX,
* although easily converted from/to TypeX, and so it would mostly work.
* However, the operators above would have to be (even more) complicated and
* accept various combinations of TypeX and TypeX::Enum (in case of a bitfield).
*
* All in all, the type mismatch (and moderately rare member function
* occurrence) does not warrant going this way at this time. Also avoid C++17
* for now, so as it stands ...
*/
#endif // GI_ENUMFLAG_HPP

View File

@@ -0,0 +1,170 @@
#ifndef GI_EXCEPTION_HPP
#define GI_EXCEPTION_HPP
#include "base.hpp"
#include "wrap.hpp"
GI_MODULE_EXPORT
namespace gi
{
namespace detail
{
inline std::logic_error
transform_error(GType tp, const char *name = nullptr)
{
auto n = g_type_name(tp);
auto msg = std::string("could not transform value to type ") +
detail::make_string(n);
if (name)
msg += std::string(" of property \'") + name + "\'";
return std::invalid_argument(msg);
}
inline std::logic_error
unknown_property_error(GType tp, const gchar *property)
{
auto n = g_type_name(tp);
auto msg = std::string("object of type ") + detail::make_string(n) +
" does not have property \'" + detail::make_string(property) +
"\'";
return std::invalid_argument(msg);
}
inline std::logic_error
unknown_signal_error(GType tp, const std::string &name)
{
auto n = g_type_name(tp);
auto msg = std::string("object of type ") + detail::make_string(n) +
" does not have signal \'" + name + "\'";
return std::invalid_argument(msg);
}
inline std::logic_error
invalid_signal_callback_error(
GType tp, const std::string &name, const std::string &_msg)
{
auto n = g_type_name(tp);
auto msg = std::string("invalid callback for signal ") + n + "::" + name +
"; " + _msg;
return std::invalid_argument(msg);
}
// partially generated GError wrapper
class Error : public gi::detail::GBoxedWrapperBase<Error, GError>
{
typedef gi::detail::GBoxedWrapperBase<Error, GError> super_type;
public:
Error(GError *obj = nullptr) : super_type(obj) {}
static GType get_type_() G_GNUC_CONST { return g_error_get_type(); }
// use with care; dangling reference caution applies here
gint &code_() { return gobj_()->code; }
const gint &code_() const { return gobj_()->code; }
// use with care; dangling reference caution applies here
gi::cstring_v message_() const { return gobj_()->message; }
// gboolean g_error_matches (const GError* error, GQuark domain, gint code);
inline bool matches(GQuark domain, gint code) const
{
return g_error_matches(gobj_(), domain, code);
}
}; // class
} // namespace detail
namespace repository
{
namespace GLib
{
class Error_Ref;
class Error
: public std::runtime_error,
public detail::GBoxedWrapper<Error, ::GError, detail::Error, Error_Ref>
{
typedef std::runtime_error super;
static inline std::string make_message(GError *error)
{
return error ? detail::make_string(g_quark_to_string(error->domain)) +
": " + detail::make_string(error->message) + "(" +
std::to_string(error->code) + ")"
: "";
}
public:
explicit Error(GError *obj = nullptr) : super(make_message(obj))
{
data_ = obj;
}
// GError* g_error_new_literal (GQuark domain, gint code, const gchar*
// message);
static inline Error new_literal(
GQuark domain, gint code, const std::string &message)
{
return Error(g_error_new_literal(
domain, code, gi::unwrap(message, gi::transfer_none)));
}
// GError* g_error_copy (const GError* error);
inline Error copy() const { return Error(g_error_copy(gobj_())); }
// override wrap since we are no longer in a simple single-base case
template<typename Cpp, typename Enable = typename std::enable_if<
std::is_base_of<Error, Cpp>::value>::type>
static Cpp wrap(const typename Cpp::BaseObjectType *obj)
{
static_assert(sizeof(Cpp) == sizeof(Error), "type wrap not supported");
Error w(const_cast<GError *>(obj));
return std::move(*static_cast<Cpp *>(&w));
}
};
class Error_Ref
: public gi::detail::GBoxedRefWrapper<GLib::Error, ::GError, detail::Error>
{
typedef gi::detail::GBoxedRefWrapper<GLib::Error, ::GError, detail::Error>
super_type;
using super_type::super_type;
// GError* g_error_copy (const GError* error);
inline Error copy() const { return Error(g_error_copy(gobj_())); }
};
} // namespace GLib
template<>
struct declare_cpptype_of<GError>
{
typedef GLib::Error type;
};
} // namespace repository
inline void
check_error(GError *error)
{
if (error)
detail::try_throw(repository::GLib::Error(error));
}
namespace detail
{
inline repository::GLib::Error
missing_symbol_error(const std::string &symbol)
{
::GQuark domain = g_quark_from_static_string("gi-error-quark");
auto error =
g_error_new(domain, 0, "could not find symbol %s", symbol.c_str());
return repository::GLib::Error(error);
}
} // namespace detail
} // namespace gi
#endif // GI_EXCEPTION_HPP

View File

@@ -0,0 +1,112 @@
#ifndef GI_EXPECTED_HPP
#define GI_EXPECTED_HPP
#include "base.hpp"
#include "exception.hpp"
GI_MODULE_EXPORT
namespace gi
{
// alias so we might route to a std type some day ...
template<typename T, typename E>
#ifdef expected_lite_VERSION
using expected = nonstd::expected<T, E>;
#else
using expected = std::expected<T, E>;
#endif
template<typename E>
#ifdef expected_lite_VERSION
using unexpected = nonstd::unexpected_type<E>;
#else
using unexpected = std::unexpected<E>;
#endif
// standardize on glib error
template<typename T>
using result = expected<T, repository::GLib::Error>;
namespace detail
{
// only use nonstd if it does not delegate to std (in incomplete way)
#if defined(expected_lite_VERSION) && !nsel_USES_STD_EXPECTED
inline unexpected<repository::GLib::Error>
make_unexpected(repository::GLib::Error error)
{
assert(error);
return nonstd::make_unexpected(std::move(error));
}
#else
inline unexpected<repository::GLib::Error>
make_unexpected(repository::GLib::Error error)
{
assert(error);
return std::unexpected<repository::GLib::Error>(std::move(error));
}
#endif
inline unexpected<repository::GLib::Error>
make_unexpected(GError *error)
{
assert(error);
return make_unexpected(repository::GLib::Error(error));
}
} // namespace detail
// no forwarding reference; T must be non-reference type
template<typename T>
result<T>
make_result(T t, GError *error)
{
if (error)
return detail::make_unexpected(error);
return t;
}
// rough helpers to unwrap result/expected
// unwrap by move
template<typename T>
T
expect(gi::result<T> &&t)
{
if (!t) {
g_critical("error result %s", t.error().what());
detail::try_throw(std::move(t.error()));
}
return std::move(*t);
}
namespace detail
{
template<typename T>
void test_result(const gi::result<T> &);
template<typename T>
int test_result(const T &);
template<typename T>
using is_result = std::is_same<void,
decltype(test_result(std::forward<T>(std::declval<T>())))>;
} // namespace detail
// should only be used for a non-result
// (e.g. avoid l-value result ending up here)
template<typename T, typename Enable = typename std::enable_if<
!detail::is_result<T>::value>::type>
T
expect(T &&t)
{
return std::forward<T>(t);
}
template<typename T>
struct rv
{
#if GI_DL && GI_EXPECTED
using type = gi::result<T>;
#else
using type = T;
#endif
};
} // namespace gi
#endif // GI_EXPECTED_HPP

20
cmake/external/glib/cppgir/gi/gi.hpp vendored Normal file
View File

@@ -0,0 +1,20 @@
#ifndef GI_HPP
#define GI_HPP
#include "base.hpp"
#include "container.hpp"
#include "enumflag.hpp"
#include "exception.hpp"
#include "expected.hpp"
#include "object.hpp"
#include "objectclass.hpp"
#include "string.hpp"
// check that include path has been setup properly to include override
#if defined(__has_include)
#if !__has_include(<glib/glib_extra_def.hpp>)
#warning "overrides not found in include path"
#endif
#endif
#endif // GI_HPP

283
cmake/external/glib/cppgir/gi/gi_inc.hpp vendored Normal file
View File

@@ -0,0 +1,283 @@
#ifndef GI_INC_HPP
#define GI_INC_HPP
// gi preprocessor/macro interface
// aka global module fragment part
#define GI_VERSION_MAJAOR (2)
#define GI_VERSION_MINOR (0)
#define GI_VERSION_MICRO (0)
#ifdef GI_INLINE
#define GI_INLINE_DECL inline
#else
#define GI_INLINE_DECL
#endif
// == we could be included in some module setting
#ifdef GI_MODULE_IN_INTERFACE
#if __cplusplus < 201703L
#error "need at least C++17 for modules"
#endif
#define GI_MODULE_EXPORT export
#define GI_MODULE_INLINE inline
#define GI_MODULE_STATIC_OR_INLINE inline
#else // GI_MODULE_IN_INTERFACE
#define GI_MODULE_EXPORT
#define GI_MODULE_INLINE
#define GI_MODULE_STATIC_OR_INLINE static
#endif
// only used in module context
#ifdef GI_MODULE_EXTERN
#define GI_MODULE_EXTERN_BEGIN extern "C++" {
#define GI_MODULE_EXTERN_END }
#else
#define GI_MODULE_EXTERN_BEGIN
#define GI_MODULE_EXTERN_END
#endif
// related; another clang warning
// https://github.com/llvm/llvm-project/issues/68615
// `#include <filename>' attaches the declarations to the named module`
#ifdef __clang__
#define GI_MODULE_DISABLE_WARN_BEGIN \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Winclude-angled-in-module-purview\"")
#define GI_MODULE_DISABLE_WARN_END _Pragma("GCC diagnostic pop")
#else
#define GI_MODULE_DISABLE_WARN_BEGIN
#define GI_MODULE_DISABLE_WARN_END
#endif
#define GI_MODULE_BEGIN \
GI_MODULE_DISABLE_WARN_BEGIN \
GI_MODULE_EXTERN_BEGIN
#define GI_MODULE_END \
GI_MODULE_EXTERN_END \
GI_MODULE_DISABLE_WARN_BEGIN
// ==
// lots of declarations might be attributed as deprecated,
// but not so annotated, so let's avoid warning floods
// also handle complaints about const qualified casts
// (due to silly const qualified scalar parameters)
#define GI_DISABLE_DEPRECATED_WARN_BEGIN \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wignored-qualifiers\"")
#define GI_DISABLE_DEPRECATED_WARN_END \
_Pragma("GCC diagnostic pop") _Pragma("GCC diagnostic pop")
// typically clang might warn but gcc might complain about pragma clang ...
#ifdef GI_CLASS_IMPL_PRAGMA
#ifndef GI_CLASS_IMPL_BEGIN
#define GI_CLASS_IMPL_BEGIN \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Woverloaded-virtual\"")
#endif
#ifndef GI_CLASS_IMPL_END
#define GI_CLASS_IMPL_END _Pragma("GCC diagnostic pop")
#endif
#else
#define GI_CLASS_IMPL_BEGIN
#define GI_CLASS_IMPL_END
#endif
// attempt to auto-discover exception support:
#ifndef GI_CONFIG_EXCEPTIONS
#if defined(_MSC_VER)
#include <cstddef> // for _HAS_EXCEPTIONS
#endif
#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS)
#define GI_CONFIG_EXCEPTIONS 1
#else
#define GI_CONFIG_EXCEPTIONS 0
#endif
#endif
#include <cstddef>
#include <exception>
#include <functional>
#include <limits>
#include <memory>
#include <string>
#include <tuple>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <iterator>
#include <map>
#include <vector>
#if __has_include("nonstd/expected.hpp")
#if __cpp_concepts >= 202002L
// this is also required in gcc's libstdc++ expected
#else
// so if that is missing, then prevent delegation to <expected>
#define nsel_CONFIG_SELECT_EXPECTED nsel_EXPECTED_NONSTD
#endif
#include "nonstd/expected.hpp"
#else
#include <expected>
#endif
#if __cplusplus >= 201703L
#include <optional>
#include <string_view>
#endif
// define << operator if not unwanted
#ifndef GI_NO_STRING_IOS
#include <iostream>
#endif
#if GI_DL
#include <dlfcn.h>
#endif
#include <assert.h>
#include <string.h>
#include <glib-object.h>
#include <glib.h>
#include <gobject/gvaluecollector.h>
// various macros from all over the place
// sadly, module refactor means breaking things apart
// exception
// exception specification is generated according to settings and situation
// some derived code (e.g. overrides) may need to follow suit accordingly
#if GI_EXPECTED
// no exception if reported through expected
#define GI_NOEXCEPT_DECL(nonthrowing) noexcept
#elif GI_DL
// otherwise, everything can start failing if resolved at runtime
#define GI_NOEXCEPT_DECL(nonthrowing)
#else
// otherwise, depends on whether (wrapped) function is (GError) throwing
#define GI_NOEXCEPT_DECL(nonthrowing) noexcept(nonthrowing)
#endif
// callback
#define GI_CB_ARG_CALLBACK_CUSTOM(Type, CF_CType, CF_handler) \
struct Type \
{ \
using handler_cb_type = CF_CType; \
static constexpr auto handler = CF_handler; \
}
// boxed
// should be used within gi.repository namespace
#define GI_ENABLE_BOXED_COPY(CType) \
template<> \
struct enable_boxed_copy<CType> : public std::true_type \
{};
// enumflag
#define GI_FLAG_OPERATORS(FlagType) \
inline FlagType operator|(FlagType lhs, FlagType rhs) \
{ \
return static_cast<FlagType>( \
static_cast<unsigned>(lhs) | static_cast<unsigned>(rhs)); \
} \
\
inline FlagType operator&(FlagType lhs, FlagType rhs) \
{ \
return static_cast<FlagType>( \
static_cast<unsigned>(lhs) & static_cast<unsigned>(rhs)); \
} \
\
inline FlagType operator^(FlagType lhs, FlagType rhs) \
{ \
return static_cast<FlagType>( \
static_cast<unsigned>(lhs) ^ static_cast<unsigned>(rhs)); \
} \
\
inline FlagType operator~(FlagType flags) \
{ \
return static_cast<FlagType>(~static_cast<unsigned>(flags)); \
} \
\
inline FlagType &operator|=(FlagType &lhs, FlagType rhs) \
{ \
return (lhs = static_cast<FlagType>( \
static_cast<unsigned>(lhs) | static_cast<unsigned>(rhs))); \
} \
\
inline FlagType &operator&=(FlagType &lhs, FlagType rhs) \
{ \
return (lhs = static_cast<FlagType>( \
static_cast<unsigned>(lhs) & static_cast<unsigned>(rhs))); \
} \
\
inline FlagType &operator^=(FlagType &lhs, FlagType rhs) \
{ \
return (lhs = static_cast<FlagType>( \
static_cast<unsigned>(lhs) ^ static_cast<unsigned>(rhs))); \
}
#define GI_ENUM_NUMERIC(EnumType) \
inline std::underlying_type<EnumType>::type operator+(EnumType e) \
{ \
return static_cast<std::underlying_type<EnumType>::type>(e); \
}
// objectclass
// helper macro to obtain data from factory (if provided)
#define GI_MEMBER_INIT_DATA(ClassType, factory) \
(factory ? ((ClassType(*)())(factory))() : ClassType());
// conflicts might arise between interfaces and/or class
// generate some dummy check types to force failure
#define GI_MEMBER_CHECK_CONFLICT(member) _check_member_conflict_##member
// generated code tries to detect a defined member in SubClass as follows
#define GI_MEMBER_DEFAULT_HAS_DEFINITION(BaseDef, member) \
template<typename SubClass> \
constexpr static bool has_definition(const member##_t *, const SubClass *) \
{ \
/* the use of conflict type check is only to trigger a compiler error \
* if there is such a conflict \
* in that case, manual specification of definitions are needed \
* (which will then avoid this code path instantiation) \
* (type should never be void, so merely serves as dummy check) \
*/ \
return std::is_void<typename SubClass::GI_MEMBER_CHECK_CONFLICT( \
member)>::value || \
!std::is_same<decltype(&BaseDef::member##_), \
decltype(&SubClass::member##_)>::value; \
}
// helper macro used in generated code
#define GI_MEMBER_DEFINE(BaseDef, member) \
struct member##_tag; \
using member##_t = detail::member_type<member##_tag>; \
member##_t member; \
GI_MEMBER_DEFAULT_HAS_DEFINITION(BaseDef, member)
// the automated way might/will fail in case of overload resolution failure
// (due to member conflicts with interfaces)
// so the following can be used to specify definition situation
// should be used in an inner struct DefinitionData in the SubClass
#define GI_DEFINES_MEMBER(BaseDef, member, _defines) \
template<typename SubClass> \
constexpr static bool defines( \
const BaseDef::TypeInitData::member##_t *, const SubClass *) \
{ \
return _defines; \
}
// uses function overload on all of the above to determine
// if member of DefData is defined/overridden in SubClass
// (and should then be registered in the class/interface struct)
#define GI_MEMBER_HAS_DEFINITION(SubClass, DefData, member) \
DefData::defines((member##_t *)(nullptr), (SubClass *)(nullptr))
#endif // GI_INC_HPP

694
cmake/external/glib/cppgir/gi/object.hpp vendored Normal file
View File

@@ -0,0 +1,694 @@
#ifndef GI_OBJECT_HPP
#define GI_OBJECT_HPP
#include "callback.hpp"
#include "container.hpp"
#include "exception.hpp"
#include "objectbase.hpp"
#include "paramspec.hpp"
#include "value.hpp"
GI_MODULE_EXPORT
namespace gi
{
namespace detail
{
// helper
// signal argument connect/emit helper;
// turn (C++) argument into a GType or GValue
// most arguments are inputs (with specific GType),
// but some arguments are used as output with G_TYPE_POINTER (e.g. int*)
// which are mapped to (e.g.) int* or int& in C++ signature
template<typename Arg, bool DECAY>
struct signal_arg
{
static GType get_type() { return traits::gtype<Arg>::get_type(); }
static detail::Value make(Arg arg)
{
return detail::Value(std::forward<Arg>(arg));
}
};
// re-route e.g. const std::string& cases
template<typename Arg>
struct signal_arg<const Arg &, false> : public signal_arg<const Arg &, true>
{};
template<typename Arg>
struct signal_arg<Arg &, false>
{
static GType get_type() { return G_TYPE_POINTER; }
static detail::Value make(Arg &arg)
{
return signal_arg<Arg *, false>::make(&arg);
}
};
template<typename Arg>
struct signal_arg<Arg *, false>
{
static GType get_type() { return G_TYPE_POINTER; }
static detail::Value make(Arg *arg)
{
// (size of) wrapper argument should match wrappee
static_assert(sizeof(typename traits::ctype<Arg>::type) == sizeof(Arg), "");
// the above should suffice for proper handling
// however, in these output cases, transfer should also be considered,
// which is not (yet) available here
// (but could be passed along similar to callback argument info)
// so, restrict to plain cases for now
static_assert(traits::is_plain<Arg>::value, "");
return detail::Value(gpointer(arg));
}
};
// returns -size if signed numeric, +size if unsigned numeric, otherwise 0
inline int
get_number_size_signed(GType type)
{
// note; these are generally lower (absolute) bounds
// at least it works in the context where it is used below
#define GI_HANDLE_TYPE_SWITCH(cpptype, g_type, factor) \
case g_type: \
return factor * int(sizeof(cpptype));
switch (type) {
GI_HANDLE_TYPE_SWITCH(gchar, G_TYPE_CHAR, -1)
GI_HANDLE_TYPE_SWITCH(guchar, G_TYPE_UCHAR, 1)
GI_HANDLE_TYPE_SWITCH(gint, G_TYPE_INT, -1)
GI_HANDLE_TYPE_SWITCH(guint, G_TYPE_UINT, 1)
GI_HANDLE_TYPE_SWITCH(glong, G_TYPE_LONG, -1)
GI_HANDLE_TYPE_SWITCH(gulong, G_TYPE_ULONG, 1)
GI_HANDLE_TYPE_SWITCH(gint64, G_TYPE_INT64, -1)
GI_HANDLE_TYPE_SWITCH(guint64, G_TYPE_UINT64, 1)
}
#undef GI_HANDLE_TYPE_SWITCH
return 0;
}
// glib type systems treats G_TYPE_INT64 as distinct from the other types
// in practice, however, quite likely C gint64 == long
inline bool
compatible_type(GType expected, GType actual)
{
if (expected == G_TYPE_BOOLEAN)
return std::abs(get_number_size_signed(actual)) == sizeof(gboolean);
auto ssa_e = get_number_size_signed(expected);
auto ssa_a = get_number_size_signed(actual);
return ssa_e == ssa_a;
}
inline void
check_signal_type(GType tp, const gi::cstring_v name, GType return_type,
GType *param_types, guint n_params)
{
const char *errmsg("expected ");
auto check_types = [tp, &name, &errmsg](const std::string &desc,
GType expected, GType actual) {
// normalize
expected &= ~G_SIGNAL_TYPE_STATIC_SCOPE;
actual &= ~G_SIGNAL_TYPE_STATIC_SCOPE;
if (expected == actual || compatible_type(expected, actual) ||
g_type_is_a(expected, actual))
return;
std::string msg = errmsg;
msg += desc + " type ";
msg += detail::make_string(g_type_name(expected)) + " != ";
msg += detail::make_string(g_type_name(actual));
detail::try_throw(invalid_signal_callback_error(tp, name, msg));
};
// determine signal (detail)
guint id;
GQuark detail;
if (!g_signal_parse_name(name.c_str(), tp, &id, &detail, false) || (id == 0))
detail::try_throw(unknown_signal_error(tp, name));
// get signal info
GSignalQuery query;
g_signal_query(id, &query);
// check
if (n_params != query.n_params + 1) {
auto msg = std::string(errmsg) + "argument count ";
msg += std::to_string(query.n_params);
msg += " != " + std::to_string(n_params);
detail::try_throw(invalid_signal_callback_error(tp, name, msg));
}
check_types("return", query.return_type, return_type);
check_types("instance", query.itype, param_types[0]);
const std::string arg("argument ");
for (guint i = 0; i < query.n_params; ++i)
check_types(
arg + std::to_string(i + 1), query.param_types[i], param_types[i + 1]);
}
template<typename G>
struct signal_type;
template<typename R, typename... Args>
struct signal_type<R(Args...)>
{
static void check(GType tp, const gi::cstring_v name)
{
// capture type info and delegate
const int argcount = sizeof...(Args);
GType ti[] = {signal_arg<Args, false>::get_type()...};
check_signal_type(tp, name, traits::gtype<R>::get_type(), ti, argcount);
}
};
// like GParameter, but with extra Value trimming
struct Parameter
{
const char *name;
detail::Value value;
};
#ifdef GI_OBJECT_NEWV
GI_DISABLE_DEPRECATED_WARN_BEGIN
static_assert(sizeof(Parameter) == sizeof(GParameter), "");
GI_DISABLE_DEPRECATED_WARN_END
#endif
inline void
fill_parameters(Parameter *)
{
// no-op
}
template<typename Arg, typename... Args>
inline void
fill_parameters(Parameter *param, const char *name, Arg &&arg, Args &&...args)
{
param->name = name;
param->value.init<typename std::remove_reference<Arg>::type>();
set_value(&param->value, std::forward<Arg>(arg));
fill_parameters(param + 1, std::forward<Args>(args)...);
}
} // namespace detail
#if GLIB_CHECK_VERSION(2, 54, 0)
#define GI_GOBJECT_PROPERTY_VALUE 1
#endif
namespace repository
{
/* if you have arrived here due to an ambiguous GObject reference
* (both the C typedef GObject and this namespace)
* then that can be worked-around by:
* + using _GObject (struct name instead)
* + adjust 'using namespace' style imports e.g. alias
* namespace GObject_ = gi::GObject;
* or simply do not mention GObject at all and simply use the wrappers ;-)
*/
namespace GObject
{
typedef std::vector<detail::Parameter> construct_params;
template<typename... Args>
construct_params
make_construct_params(Args &&...args)
{
const int nparams = sizeof...(Args) / 2;
construct_params parameters;
parameters.resize(nparams);
detail::fill_parameters(parameters.data(), std::forward<Args>(args)...);
return parameters;
}
class Object : public detail::ObjectBase
{
typedef Object self;
typedef detail::ObjectBase super_type;
public:
typedef ::GObject BaseObjectType;
Object(std::nullptr_t = nullptr) : super_type() {}
BaseObjectType *gobj_() { return (BaseObjectType *)super_type::gobj_(); }
const BaseObjectType *gobj_() const
{
return (const BaseObjectType *)super_type::gobj_();
}
BaseObjectType *gobj_copy_() const
{
return (BaseObjectType *)super_type::gobj_copy_();
}
// class type
static GType get_type_() { return G_TYPE_OBJECT; }
// instance type
GType gobj_type_() const { return G_OBJECT_TYPE(gobj_()); }
// type-erased generic object creation
// transfer full return
static gpointer new_(GType gtype, const construct_params &params)
{
#ifdef GI_OBJECT_NEWV
GI_DISABLE_DEPRECATED_WARN_BEGIN
auto result =
g_object_newv(gtype, params.size(), (GParameter *)params.data());
GI_DISABLE_DEPRECATED_WARN_END
#else
std::vector<const char *> names;
std::vector<GValue> values;
names.reserve(params.size());
values.reserve(params.size());
// ownership remains in params
for (auto &&p : params) {
names.push_back(p.name);
values.emplace_back(p.value);
}
auto result = g_object_new_with_properties(
gtype, params.size(), names.data(), values.data());
#endif
// GIR says transfer full, but let's be careful and really make it so
// if likely still floating, then we assume ownership
// but if it is no longer, then it has already been stolen (e.g.
// GtkWindow), and we need to add one here
if (g_type_is_a(gtype, G_TYPE_INITIALLY_UNOWNED))
g_object_ref_sink(result);
return result;
}
// type-based generic object creation
template<typename CTYPE, typename... Args>
static auto new_(GType gtype, Args &&...args)
{
auto parameters = make_construct_params(std::forward<Args>(args)...);
auto *result = CTYPE(new_(gtype, parameters));
return gi::wrap(result, transfer_full);
}
// type-based generic object creation
// Args are a sequence of name, value
template<typename TYPE, typename... Args>
static TYPE new_(Args &&...args)
{
return new_<typename TYPE::BaseObjectType *>(
TYPE::get_type_(), std::forward<Args>(args)...);
}
// property stuff
// generic type unsafe
template<typename V>
self &set_property(ParamSpec _pspec, V &&val)
{
// additional checks
// allows for basic conversion between arithmetic types
// without worrying about those details
auto pspec = _pspec.gobj_();
detail::Value v(std::forward<V>(val));
detail::Value dest;
GValue *p = &v;
if (G_VALUE_TYPE(&v) != pspec->value_type) {
g_value_init(&dest, pspec->value_type);
if (!g_value_transform(&v, &dest))
detail::try_throw(
detail::transform_error(pspec->value_type, pspec->name));
p = &dest;
}
g_object_set_property(gobj_(), pspec->name, p);
return *this;
}
template<typename V>
self &set_property(const gi::cstring_v propname, V &&val)
{
return set_property<V>(find_property(propname, true), std::forward<V>(val));
}
template<typename V>
self &set_properties(const gi::cstring_v propname, V &&val)
{
return set_property<V>(propname, std::forward<V>(val));
}
// set a number of props
template<typename V, typename... Args>
self &set_properties(const gi::cstring_v propname, V &&val, Args... args)
{
g_object_freeze_notify(gobj_());
#if GI_CONFIG_EXCEPTIONS
try {
#endif
set_property(propname, std::forward<V>(val));
set_properties(std::forward<Args>(args)...);
#if GI_CONFIG_EXCEPTIONS
} catch (...) {
g_object_thaw_notify(gobj_());
throw;
}
#endif
g_object_thaw_notify(gobj_());
return *this;
}
#ifdef GI_GOBJECT_PROPERTY_VALUE
self &set_property(const gi::cstring_v propname, Value val)
{
g_object_set_property(gobj_(), propname.c_str(), val.gobj_());
return *this;
}
#endif
template<typename V>
V get_property(const char *propname) const
{
// this would return a ref to what is owned by stack-local v below
static_assert(!traits::is_reftype<V>::value, "dangling ref");
detail::Value v;
v.init<V>();
// the _get_ already tries to transform
// also close enough to const
g_object_get_property(const_cast<::GObject *>(gobj_()), propname, &v);
return detail::get_value<V>(&v);
}
template<typename V>
V get_property(const gi::cstring_v propname) const
{
return get_property<V>(propname.c_str());
}
#ifdef GI_GOBJECT_PROPERTY_VALUE
Value get_property(const gi::cstring_v propname) const
{
Value result;
const gchar *name = propname.c_str();
GValue *val = result.gobj_();
g_object_getv(const_cast<::GObject *>(gobj_()), 1, &name, val);
return result;
}
#endif
static ParamSpec find_property(
GType gtype, const gi::cstring_v propname, bool _throw = false)
{
GParamSpec *spec;
if (g_type_is_a(gtype, G_TYPE_INTERFACE)) {
// interface should be loaded if we have an instance here
auto vtable = g_type_default_interface_peek(gtype);
spec = g_object_interface_find_property(vtable, propname.c_str());
} else {
spec = g_object_class_find_property(
(GObjectClass *)g_type_class_peek(gtype), propname.c_str());
}
if (_throw && !spec)
detail::try_throw(
detail::unknown_property_error(gtype, propname.c_str()));
return gi::wrap(spec, transfer_none);
}
ParamSpec find_property(
const gi::cstring_v propname, bool _throw = false) const
{
return find_property(gobj_type_(), propname, _throw);
}
gi::Collection<gi::DSpan, GParamSpec *, gi::transfer_container_t>
list_properties() const
{
GParamSpec **specs;
guint nspecs = 0;
if (g_type_is_a(gobj_type_(), G_TYPE_INTERFACE)) {
// interface should be loaded if we have an instance here
auto vtable = g_type_default_interface_peek(gobj_type_());
specs = g_object_interface_list_properties(vtable, &nspecs);
} else {
specs =
g_object_class_list_properties(G_OBJECT_GET_CLASS(gobj_()), &nspecs);
}
return wrap_to<
gi::Collection<gi::DSpan, GParamSpec *, gi::transfer_container_t>>(
specs, nspecs, transfer_container);
}
// signal stuff
private:
template<typename F, typename Functor>
gulong connect_data(
const gi::cstring_v signal, Functor &&f, GConnectFlags flags)
{
// runtime signature check
detail::signal_type<F>::check(gobj_type_(), signal);
auto w = new detail::transform_signal_wrapper<F>(std::forward<Functor>(f));
// mind gcc's -Wcast-function-type
return g_signal_connect_data(gobj_(), signal.c_str(),
(GCallback)&w->wrapper, w, (GClosureNotify)(GCallback)&w->destroy,
flags);
}
public:
template<typename F, typename Functor>
gulong connect(const gi::cstring_v signal, Functor &&f)
{
return connect_data<F, Functor>(
signal, std::forward<Functor>(f), (GConnectFlags)0);
}
template<typename F, typename Functor>
gulong connect_after(const gi::cstring_v signal, Functor &&f)
{
return connect_data<F, Functor>(
signal, std::forward<Functor>(f), G_CONNECT_AFTER);
}
// TODO the object variants ??
// in case of unsupported signal signature
// connect using a plain C signature without check/transform (wrap/unwrap)
template<typename F, typename Functor>
gulong connect_unchecked(
const gi::cstring_v signal, Functor &&f, GConnectFlags flags = {})
{
auto w = new detail::callback_wrapper<F>(std::forward<Functor>(f));
// mind gcc's -Wcast-function-type
return g_signal_connect_data(gobj_(), signal.c_str(),
(GCallback)&w->wrapper, w, (GClosureNotify)(GCallback)&w->destroy,
flags);
}
void disconnect(gulong id) { g_signal_handler_disconnect(gobj_(), id); }
// Args... may be explicitly specified or deduced
// if deduced; arrange to decay/strip reference below
// if not deduced; may need to considere specified type as-is
template<typename R, bool DECAY = true, typename... Args>
R emit(const gi::cstring_v signal, Args &&...args)
{
// static constexpr bool DECAY = true;
guint id;
GQuark detail;
if (!g_signal_parse_name(signal.c_str(), gobj_type_(), &id, &detail, true))
detail::try_throw(std::out_of_range(std::string("unknown signal name: ") +
detail::make_string(signal.c_str())));
detail::Value values[] = {detail::Value(*this),
detail::signal_arg<Args, DECAY>::make(std::forward<Args>(args))...};
detail::Value retv;
retv.init<R>();
g_signal_emitv(values, id, detail, &retv);
return detail::get_value<R>(&retv);
}
void handler_block(gulong handler_id)
{
g_signal_handler_block(gobj_(), handler_id);
}
void handler_unblock(gulong handler_id)
{
g_signal_handler_unblock(gobj_(), handler_id);
}
bool handler_is_connected(gulong handler_id)
{
return g_signal_handler_is_connected(gobj_(), handler_id);
}
void stop_emission(guint id, GQuark detail)
{
g_signal_stop_emission(gobj_(), id, detail);
}
void stop_emission_by_name(const gi::cstring_v signal)
{
g_signal_stop_emission_by_name(gobj_(), signal.c_str());
}
};
} // namespace GObject
template<>
struct declare_cpptype_of<::GObject>
{
typedef repository::GObject::Object type;
};
namespace GLib
{
// predefined
typedef detail::callback<void(), gi::transfer_none_t> DestroyNotify;
} // namespace GLib
} // namespace repository
// type safe signal connection
template<typename T, typename Base = repository::GObject::Object>
class signal_proxy;
template<typename R, typename Instance, typename... Args, typename Base>
class signal_proxy<R(Instance, Args...), Base>
{
protected:
typedef R(CppSig)(Instance, Args...);
Base object_;
gi::cstring name_;
public:
typedef CppSig function_type;
typedef detail::connectable<function_type> slot_type;
signal_proxy(Base owner, gi::cstring name)
: object_(owner), name_(std::move(name))
{}
template<typename Functor>
gulong connect(Functor &&f)
{
return object_.template connect<CppSig>(name_, std::forward<Functor>(f));
}
template<typename Functor>
gulong connect_after(Functor &&f)
{
return object_.template connect_after<CppSig>(
name_, std::forward<Functor>(f));
}
R emit(Args... args)
{
return object_.template emit<R, false, Args...>(
name_, std::forward<Args>(args)...);
}
template<typename Functor>
slot_type slot(Functor &&f)
{
return slot_type(std::forward<Functor>(f));
}
};
// type safe property setting
template<typename T, typename Base = repository::GObject::Object>
class property_proxy
{
typedef property_proxy self;
typedef repository::GObject::ParamSpec ParamSpec;
protected:
Base object_;
ParamSpec pspec_;
public:
property_proxy(Base owner, ParamSpec pspec) : object_(owner), pspec_(pspec) {}
property_proxy(Base owner, const gi::cstring_v name)
: property_proxy(owner, owner.find_property(name, true))
{}
void set(T v) { object_.set_property(pspec_, std::move(v)); }
self &operator=(T v)
{
set(v);
return *this;
}
T get() const
{
return object_.template get_property<T>(pspec_.gobj_()->name);
}
ParamSpec param_spec() const { return pspec_; }
signal_proxy<void(Base, ParamSpec)> signal_notify() const
{
return signal_proxy<void(Base, ParamSpec)>(
object_, gi::cstring_v("notify::") + gi::cstring_v(pspec_.name_()));
}
};
template<typename T, typename Base = repository::GObject::Object>
class property_proxy_read : private property_proxy<T, Base>
{
typedef property_proxy<T, Base> super;
public:
using super::get;
using super::property_proxy;
};
template<typename T, typename Base = repository::GObject::Object>
class property_proxy_write : private property_proxy<T, Base>
{
typedef property_proxy<T, Base> super;
public:
using super::property_proxy;
using super::set;
using super::operator=;
};
// interface (ptr) is wrapped the same way,
// as it is essentially a ptr to implementing object
// TODO use other intermediate base ??
using InterfaceBase = repository::GObject::Object;
namespace repository
{
namespace GObject
{
// connection helpers
namespace internal
{
class SignalConnection : public detail::connection_impl
{
public:
SignalConnection(gulong id, detail::connection_status s, Object object)
: connection_impl(id, s), object_(object)
{}
void disconnect() { object_.disconnect(id_); }
private:
Object object_;
};
} // namespace internal
using SignalConnection = detail::connection<internal::SignalConnection>;
using SignalScopedConnection = detail::scoped_connection<SignalConnection>;
} // namespace GObject
} // namespace repository
// connection callback type
template<typename G>
using slot = detail::connectable<G>;
template<typename G>
inline repository::GObject::SignalConnection
make_connection(
gulong id, const gi::slot<G> &s, repository::GObject::Object object)
{
using repository::GObject::SignalConnection;
return SignalConnection(id, s.connection(), object);
}
} // namespace gi
#endif // GI_OBJECT_HPP

View File

@@ -0,0 +1,172 @@
#ifndef GI_OBJECTBASE_HPP
#define GI_OBJECTBASE_HPP
#include "gi_inc.hpp"
GI_MODULE_EXPORT
namespace gi
{
namespace detail
{
struct GObjectFuncs
{
static void *ref(void *data) { return g_object_ref(data); }
static void *sink(void *data) { return g_object_ref_sink(data); }
static void free(void *data) { g_object_unref(data); }
static void *float_(void *data)
{
g_object_force_floating((GObject *)data);
return g_object_ref(data);
}
};
template<typename Funcs, typename CType = void>
class Wrapper
{
protected:
CType *data_ = nullptr;
static CType *_ref(CType *data) { return data ? Funcs::ref(data) : data; }
static CType *_sink(CType *data) { return data ? Funcs::sink(data) : data; }
static CType *_float(CType *data)
{
return data ? Funcs::float_(data) : data;
}
static void _deleter(CType *&data)
{
if (data)
Funcs::free(data);
}
public:
Wrapper(decltype(data_) d = nullptr, bool own = true, bool sink = true)
: data_(own ? d : (sink ? _sink(d) : _ref(d)))
{}
~Wrapper() { _deleter(data_); }
Wrapper(const Wrapper &other)
{
_deleter(data_);
data_ = _ref(other.data_);
}
Wrapper(Wrapper &&other) noexcept
{
_deleter(data_);
data_ = other.data_;
other.data_ = nullptr;
}
explicit operator bool() const { return (bool)data_; }
Wrapper &operator=(const Wrapper &other)
{
if (&other != this) {
_deleter(data_);
data_ = _ref(other.data_);
}
return *this;
}
Wrapper &operator=(Wrapper &&other) noexcept
{
if (&other != this) {
_deleter(data_);
data_ = other.data_;
other.data_ = nullptr;
}
return *this;
}
bool operator==(const Wrapper &other) const { return data_ == other.data_; }
bool operator==(std::nullptr_t o) const
{
(void)o;
return data_ == o;
}
bool operator!=(const Wrapper &other) const { return data_ != other.data_; }
bool operator!=(std::nullptr_t o) const { return data_ != o; }
CType *gobj_() { return this->data_; }
const CType *gobj_() const { return this->data_; }
};
class wrapper_tag
{};
template<typename CType, typename Funcs, GType GTYPE_>
class WrapperBase : public Wrapper<Funcs>, public wrapper_tag
{
typedef WrapperBase self;
typedef Wrapper<Funcs> super_type;
public:
typedef CType BaseObjectType;
BaseObjectType *gobj_() { return (BaseObjectType *)this->data_; }
const BaseObjectType *gobj_() const
{
return (const BaseObjectType *)this->data_;
}
BaseObjectType *gobj_copy_() const
{
return (BaseObjectType *)self::_ref(this->data_);
}
BaseObjectType *gobj_float_() const
{
return (BaseObjectType *)self::_float(this->data_);
}
BaseObjectType *release_()
{
void *r = nullptr;
std::swap(this->data_, r);
return (BaseObjectType *)r;
}
static GType get_type_() { return GTYPE_; }
GType gobj_type_() { return GTYPE_; }
WrapperBase(BaseObjectType *p = nullptr, bool own = true, bool argout = true)
: super_type(p, own, argout)
{}
WrapperBase(const self &other) = default;
WrapperBase(self &&other) = default;
self &operator=(const self &other) = default;
self &operator=(self &&other) = default;
// always arrange to sink by default nowadays
template<typename Cpp>
static Cpp wrap(
const typename Cpp::BaseObjectType *obj, bool own, bool argout = true)
{
static_assert(sizeof(Cpp) == sizeof(self), "type wrap not supported");
static_assert(std::is_base_of<self, Cpp>::value, "type wrap not supported");
WrapperBase w((self::BaseObjectType *)(obj), own, argout);
return std::move(*static_cast<Cpp *>(&w));
}
};
typedef WrapperBase<void, GObjectFuncs, G_TYPE_NONE> ObjectBase;
// foundation for Variant that will be generated
struct GVariantFuncs
{
static void *ref(void *data) { return g_variant_ref((GVariant *)data); }
static void *sink(void *data) { return g_variant_ref_sink((GVariant *)data); }
static void free(void *data) { g_variant_unref((GVariant *)data); }
static void *float_(void *data) { return data; }
};
typedef WrapperBase<GVariant, GVariantFuncs, G_TYPE_VARIANT> VariantWrapper;
} // namespace detail
} // namespace gi
#endif // GI_OBJECTBASE_HPP

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,242 @@
#ifndef GI_PARAMSPEC_HPP
#define GI_PARAMSPEC_HPP
#include "objectbase.hpp"
#include "value.hpp"
GI_MODULE_EXPORT
namespace gi
{
// slightly nasty; will be generated
namespace repository
{
namespace GObject
{
enum class ParamFlags : std::underlying_type<::GParamFlags>::type;
}
} // namespace repository
namespace detail
{
struct GParamSpecFuncs
{
static void *ref(void *data) { return g_param_spec_ref((GParamSpec *)data); }
static void *sink(void *data)
{
return g_param_spec_ref_sink((GParamSpec *)data);
}
static void free(void *data) { g_param_spec_unref((GParamSpec *)data); }
static void *float_(void *data) { return data; }
};
using ParamFlags = repository::GObject::ParamFlags;
// helper paramspec type
template<typename T>
struct param_spec_constructor;
#define GI_DECLARE_PARAM_SPEC(cpptype, suffix) \
template<> \
struct param_spec_constructor<cpptype> \
{ \
typedef std::true_type range_type; \
static const constexpr decltype(&g_param_spec_##suffix) new_ = \
g_param_spec_##suffix; \
};
GI_DECLARE_PARAM_SPEC(char, char)
GI_DECLARE_PARAM_SPEC(unsigned char, uchar)
GI_DECLARE_PARAM_SPEC(int, int)
GI_DECLARE_PARAM_SPEC(unsigned int, uint)
GI_DECLARE_PARAM_SPEC(long, long)
GI_DECLARE_PARAM_SPEC(unsigned long, ulong)
GI_DECLARE_PARAM_SPEC(long long, int64)
GI_DECLARE_PARAM_SPEC(unsigned long long, uint64)
GI_DECLARE_PARAM_SPEC(float, float)
GI_DECLARE_PARAM_SPEC(double, double)
#undef GI_DECLARE_PARAM_SPEC
// specialize appropriately with static new_ member
template<typename T, typename Enable = void>
struct ParamSpecFactory;
template<typename T>
struct ParamSpecFactory<T,
typename std::enable_if<param_spec_constructor<T>::range_type::value>::type>
{
static GParamSpec *new_(const gi::cstring_v name, const gi::cstring_v nick,
const gi::cstring_v blurb, T min, T max, T _default,
ParamFlags flags = (ParamFlags)G_PARAM_READWRITE)
{
return param_spec_constructor<T>::new_(name.c_str(), nick.c_str(),
blurb.c_str(), min, max, _default, (GParamFlags)flags);
}
static GParamSpec *new_(const gi::cstring_v name, const gi::cstring_v nick,
const gi::cstring_v blurb, T min, T max,
ParamFlags flags = (ParamFlags)G_PARAM_READWRITE)
{
return new_(name, nick, blurb, min, max, T{}, flags);
}
};
template<>
struct ParamSpecFactory<bool>
{
static GParamSpec *new_(const gi::cstring_v name, const gi::cstring_v nick,
const gi::cstring_v blurb, bool _default,
ParamFlags flags = (ParamFlags)G_PARAM_READWRITE)
{
return g_param_spec_boolean(name.c_str(), nick.c_str(), blurb.c_str(),
_default, (GParamFlags)flags);
}
};
template<>
struct ParamSpecFactory<gpointer>
{
static GParamSpec *new_(const gi::cstring_v name, const gi::cstring_v nick,
const gi::cstring_v blurb,
ParamFlags flags = (ParamFlags)G_PARAM_READWRITE)
{
return g_param_spec_pointer(
name.c_str(), nick.c_str(), blurb.c_str(), (GParamFlags)flags);
}
};
template<>
struct ParamSpecFactory<std::string>
{
static GParamSpec *new_(const gi::cstring_v name, const gi::cstring_v nick,
const gi::cstring_v blurb, const gi::cstring_v _default,
ParamFlags flags = (ParamFlags)G_PARAM_READWRITE)
{
return g_param_spec_string(name.c_str(), nick.c_str(), blurb.c_str(),
_default.c_str(), (GParamFlags)flags);
}
};
template<>
struct ParamSpecFactory<gi::cstring> : public ParamSpecFactory<std::string>
{};
template<typename T>
struct ParamSpecFactory<T,
typename std::enable_if<traits::is_object<T>::value>::type>
{
static GParamSpec *new_(const gi::cstring_v name, const gi::cstring_v nick,
const gi::cstring_v blurb,
ParamFlags flags = (ParamFlags)G_PARAM_READWRITE)
{
return g_param_spec_object(name.c_str(), nick.c_str(), blurb.c_str(),
traits::gtype<T>::get_type(), (GParamFlags)flags);
}
};
template<typename T>
struct ParamSpecFactory<T,
typename std::enable_if<traits::is_boxed<T>::value>::type>
{
static GParamSpec *new_(const gi::cstring_v name, const gi::cstring_v nick,
const gi::cstring_v blurb,
ParamFlags flags = (ParamFlags)G_PARAM_READWRITE)
{
return g_param_spec_boxed(name.c_str(), nick.c_str(), blurb.c_str(),
traits::gtype<T>::get_type(), (GParamFlags)flags);
}
};
template<typename T>
struct ParamSpecFactory<T,
typename std::enable_if<traits::is_enum_or_bitfield<T>::value>::type>
{
static GParamSpec *new_(const gi::cstring_v name, const gi::cstring_v nick,
const gi::cstring_v blurb, guint _default = 0,
ParamFlags flags = (ParamFlags)G_PARAM_READWRITE)
{
GType t = traits::gtype<T>::get_type();
// FIXME compile-time determination rather than dynamic ??
return G_TYPE_IS_FLAGS(t)
? g_param_spec_flags(name.c_str(), nick.c_str(), blurb.c_str(),
t, _default, (GParamFlags)flags)
: g_param_spec_enum(name.c_str(), nick.c_str(), blurb.c_str(), t,
_default, (GParamFlags)flags);
}
};
} // namespace detail
namespace repository
{
// slightly nasty
namespace GObject
{
class ParamSpec;
}
template<>
struct declare_cpptype_of<GParamSpec>
{
typedef GObject::ParamSpec type;
};
namespace GObject
{
class ParamSpec : public detail::WrapperBase<GParamSpec,
detail::GParamSpecFuncs, G_TYPE_PARAM>
{
typedef WrapperBase<GParamSpec, detail::GParamSpecFuncs, G_TYPE_PARAM> super;
public:
ParamSpec(std::nullptr_t = nullptr) {}
template<typename T, typename... Args>
static ParamSpec new_(Args &&...args)
{
return static_cast<ParamSpec &&>(
super(detail::ParamSpecFactory<T>::new_(std::forward<Args>(args)...)));
}
// special override case
static ParamSpec new_(const gi::cstring_v name, ParamSpec overridden)
{
return static_cast<ParamSpec &&>(
super(g_param_spec_override(name.c_str(), overridden.gobj_())));
}
gi::cstring_v get_blurb() { return g_param_spec_get_blurb(gobj_()); }
gi::cstring_v get_nick() { return g_param_spec_get_nick(gobj_()); }
gi::cstring_v get_name() { return g_param_spec_get_name(gobj_()); }
GQuark get_name_quark() { return g_param_spec_get_name_quark(gobj_()); }
repository::GObject::Value get_default_value()
{
return gi::wrap(g_param_spec_get_default_value(gobj_()), transfer_none);
}
ParamSpec get_redirect_target()
{
return gi::wrap(g_param_spec_get_redirect_target(gobj_()), transfer_none);
}
// struct fields
const gchar *name_() const { return gobj_()->name; }
ParamFlags value_type() const { return (ParamFlags)gobj_()->flags; }
GType value_type_() const { return gobj_()->value_type; }
GType owner_type_() const { return gobj_()->owner_type; }
};
} // namespace GObject
} // namespace repository
} // namespace gi
#endif // GI_PARAMSPEC_HPP

874
cmake/external/glib/cppgir/gi/string.hpp vendored Normal file
View File

@@ -0,0 +1,874 @@
#ifndef GI_STRING_HPP
#define GI_STRING_HPP
#include "base.hpp"
#ifdef __has_builtin
#define GI_HAS_BUILTIN(x) __has_builtin(x)
#else
#define GI_HAS_BUILTIN(x) 0
#endif
GI_MODULE_EXPORT
namespace gi
{
namespace convert
{
// generic template; specialize as needed where appropriate
template<typename From, typename To, class Enable = void>
struct converter
{
// fail in explanatory way if we end up here
static To convert(const From &)
{
static_assert(!std::is_void<Enable>::value, "unknown type conversion");
return To();
}
};
// implementation should provide some types
template<typename From, typename To, class Enable = void>
struct converter_base : public std::true_type
{
typedef From from_type;
typedef To to_type;
};
// conversion check for complete type
template<typename From, typename To, typename Enable = void>
struct is_convertible_impl : public std::false_type
{};
template<typename From, typename To>
struct is_convertible_impl<From, To,
typename std::enable_if<std::is_base_of<To, From>::value>::type>
: public std::false_type
{};
template<typename From, typename To>
struct is_convertible_impl<From, To,
typename std::enable_if<!std::is_base_of<To, From>::value &&
std::is_pointer<typename converter<From,
To>::from_type *>::value>::type>
: public std::true_type
{};
template<typename From, typename To, bool complete>
struct is_convertible_pre
{
using type = std::false_type;
};
template<typename From, typename To>
struct is_convertible_pre<From, To, true>
{
using type = is_convertible_impl<From, To>;
};
// check whether conversion possible
// reject incomplete forward declared types
template<typename From, typename To, typename Enable = void>
struct is_convertible : public is_convertible_pre<From, To,
gi::traits::is_type_complete<To>::value>::type
{};
} // namespace convert
namespace detail
{
// tag
struct String
{};
template<typename Transfer>
struct StringFuncs
{
static void _deleter(char *&p)
{
if (Transfer().value)
g_free(p);
p = nullptr;
}
static void _copy(const char *p)
{
return Transfer().value ? g_strdup(p) : p;
}
};
template<typename Transfer>
class cstr : public String
{
using self_type = cstr;
protected:
using _member_type =
typename std::conditional<std::is_same<Transfer, transfer_full_t>::value,
char, const char>::type;
_member_type *data_ = nullptr;
void clear()
{
if (Transfer().value && data_)
g_free((char *)data_);
data_ = nullptr;
}
public:
using traits_type = std::char_traits<char>;
using value_type = char;
using pointer = char *;
using const_pointer = const char *;
using reference = char &;
using const_reference = const char &;
using const_iterator = const char *;
using iterator = const_iterator;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using reverse_iterator = const_reverse_iterator;
using size_type = size_t;
using difference_type = std::ptrdiff_t;
using view_type = cstr<transfer_none_t>;
static constexpr bool is_view_type =
!std::is_same<Transfer, transfer_full_t>::value;
static constexpr size_type npos = static_cast<size_type>(-1);
constexpr cstr() noexcept : data_(nullptr) {}
constexpr cstr(std::nullptr_t) noexcept : data_(nullptr) {}
// const usually means none, so only on view type
// also, no arbitrary size is accepted here
template<typename Enable = void,
typename std::enable_if<std::is_same<Enable, void>::value &&
is_view_type>::type * = nullptr>
constexpr cstr(const char *data) : data_(data)
{}
// a single pointer, optionally specify ownership
// behave like string by default, assume no ownership of incoming pointer
template<typename LTransfer = transfer_none_t,
typename std::enable_if<
(std::is_same<LTransfer, transfer_full_t>::value ||
std::is_same<LTransfer, transfer_none_t>::value) &&
!is_view_type>::type * = nullptr>
cstr(char *data, const LTransfer &t)
: data_((t.value == transfer_full.value) || !data ? data : g_strdup(data))
{}
// pointer along with size
// always behave like string and make a copy
template<typename Enable = void,
typename std::enable_if<std::is_same<Enable, void>::value &&
!is_view_type>::type * = nullptr>
cstr(const char *data, size_t len = npos)
: data_(!data ? nullptr
: (len == npos ? g_strdup(data) : g_strndup(data, len)))
{}
// construct from any transfer variant
template<typename OTransfer>
cstr(const cstr<OTransfer> &s) : cstr(s.data())
{}
// accept from string, but NOT string_view as that may not be null-terminated
template<typename Allocator>
cstr(const std::basic_string<char, std::char_traits<char>, Allocator>
&s) noexcept
: cstr(s.data())
{}
#if __cplusplus >= 201703L
// some optional variants of above
constexpr cstr(std::nullopt_t) noexcept : data_(nullptr) {}
template<typename Allocator>
cstr(const std::optional<
std::basic_string<char, std::char_traits<char>, Allocator>> &s) noexcept
: cstr(s ? s.value().data() : nullptr)
{}
#endif
// hook extensible conversion
// (avoid instantiation and confusion with self and base types)
template<typename From,
typename NoBase = typename std::enable_if<
!std::is_base_of<self_type, From>::value>::type,
typename Enable = typename std::enable_if<
convert::is_convertible<From, self_type>::value>::type>
cstr(const From &f) : cstr(convert::converter<From, self_type>::convert(f))
{}
// custom
constexpr const_pointer gobj_() const { return data_; }
explicit constexpr operator bool() const { return data_; }
_member_type *release_()
{
auto tmp = this->data_;
this->data_ = nullptr;
return tmp;
}
// destruct / copy / assign
~cstr() { clear(); }
cstr(const cstr &other) { *this = other; }
cstr(cstr &&other) { *this = std::move(other); }
cstr &operator=(const cstr &other)
{
if (this != &other) {
clear();
data_ = Transfer().value ? g_strdup(other.data_) : other.data_;
}
return *this;
}
cstr &operator=(cstr &&other)
{
if (this != &other) {
clear();
data_ = other.data_;
other.data_ = nullptr;
}
return *this;
}
// deduced conversion to string(_view)
template<typename Destination,
typename Check = typename std::enable_if<
convert::is_convertible<self_type, Destination>::value>::type>
operator Destination() const
{
return convert::converter<self_type, Destination>::convert(*this);
}
#if __cplusplus >= 201703L
// unfortunately, conversion to std::optional picks Destination = std::string
// (which can obviously not be excluded above, or as specialization)
std::optional<std::string> opt_()
{
using To = std::optional<std::string>;
return c_str() ? To({c_str(), size()}) : To(std::nullopt);
}
#endif
// usual string(view) stuff
// iterators
constexpr const_iterator begin() const noexcept { return data_; }
constexpr const_iterator end() const noexcept
{
return data_ ? data_ + size() : nullptr;
}
constexpr const_iterator cbegin() const noexcept { return begin(); }
constexpr const_iterator cend() const noexcept { return end(); }
const_reverse_iterator rbegin() const noexcept
{
return const_reverse_iterator(end());
}
const_reverse_iterator rend() const noexcept
{
return const_reverse_iterator(begin());
}
const_reverse_iterator crbegin() const noexcept { return rbegin(); }
const_reverse_iterator crend() const noexcept { return rend(); }
// capacity
constexpr size_type size() const noexcept
{
#if GI_HAS_BUILTIN(__builtin_strlen) || \
(defined(__GNUC__) && !defined(__clang__))
return __builtin_strlen(data_);
#else
return data_ ? strlen(data_) : 0;
#endif
}
constexpr size_type length() const noexcept { return size(); }
constexpr size_type max_size() const noexcept
{
return std::numeric_limits<size_type>::max();
}
constexpr bool empty() const noexcept { return !data_ || *data_ == 0; }
// access
constexpr const_reference operator[](size_type i) const { return data_[i]; }
constexpr const_reference at(size_type i) const
{
return i < size() ? data_[i]
: (try_throw(std::out_of_range("cstr::at")), data_[i]);
}
constexpr const_reference front() const { return data_[0]; }
constexpr const_reference back() const { return data_[size() - 1]; }
constexpr const_pointer data() const noexcept { return data_; }
constexpr const_pointer c_str() const noexcept { return data_; }
// modifiers
// view only
template<typename Enable = void,
typename std::enable_if<std::is_same<Enable, void>::value &&
is_view_type>::type * = nullptr>
constexpr void remove_prefix(size_type n)
{
data_ += n;
}
constexpr void swap(self_type &s) noexcept
{
std::swap(this->data_, s.data_);
}
// operations
size_type copy(char *buf, size_type n, size_type pos = 0) const
{
auto s = size();
if (pos > s)
try_throw(std::out_of_range("cstr::copy"));
size_type rlen = (std::min)(s - pos, n);
if (rlen > 0) {
const char *start = data_ + pos;
traits_type::copy(buf, start, rlen);
}
return rlen;
}
int compare(view_type x) const noexcept
{
return g_strcmp0(data_, x.c_str());
}
int compare(const char *s) const { return compare(view_type(s)); }
// find
size_type find(view_type n, size_type pos = 0) const noexcept
{
auto s = size();
auto os = n.size();
if (os > s || pos > s - os)
return npos;
if (!os)
return pos <= s ? pos : npos;
auto loc = strstr(data_ + pos, n.data());
return loc ? loc - data_ : npos;
}
size_type find(char c, size_type pos = 0) const noexcept
{
if (pos >= size())
return npos;
auto loc = strchr(data_ + pos, c);
return loc ? loc - data_ : npos;
}
size_type find(const char *s, size_type pos = 0) const
{
return find(view_type(s), pos);
}
size_type rfind(view_type n, size_type pos = npos) const noexcept
{
auto s = size();
if (!s)
return npos;
auto os = n.size();
if (!os)
return pos == npos ? s : pos;
auto loc = g_strrstr_len(c_str(), pos, n.c_str());
return loc ? loc - data_ : npos;
}
size_type rfind(char c, size_type pos = npos) const noexcept
{
// not quite efficient, but anyways
char str[] = {c, 0};
return rfind(str, pos);
}
size_type rfind(const char *s, size_type pos = npos) const
{
return rfind(view_type(s), pos);
}
// find_first_of variants
// only provide those if std helps us out
#if __cplusplus >= 201703L
size_type find_first_of(view_type s, size_type pos = 0) const noexcept
{
return std::string_view(data_).find_first_of(s.data(), pos);
}
size_type find_first_of(char c, size_type pos = 0) const noexcept
{
return find(c, pos);
}
size_type find_first_of(const char *s, size_type pos = 0) const
{
return find_first_of(view_type(s), pos);
}
size_type find_last_of(view_type s, size_type pos = npos) const noexcept
{
return std::string_view(data_).find_last_of(s.data(), pos);
}
size_type find_last_of(char c, size_type pos = npos) const noexcept
{
return rfind(c, pos);
}
size_type find_last_of(const char *s, size_type pos = npos) const
{
return find_last_of(view_type(s), pos);
}
size_type find_first_not_of(view_type s, size_type pos = 0) const noexcept
{
return std::string_view(data_).find_first_not_of(s.data(), pos);
}
size_type find_first_not_of(char c, size_type pos = 0) const noexcept
{
return std::string_view(data_).find_first_not_of(c, pos);
}
size_type find_first_not_of(const char *s, size_type pos = 0) const
{
return find_first_not_of(view_type(s), pos);
}
size_type find_last_not_of(view_type s, size_type pos = npos) const noexcept
{
return std::string_view(data_).find_last_not_of(s.data(), pos);
}
size_type find_last_not_of(char c, size_type pos = npos) const noexcept
{
return std::string_view(data_).find_last_not_of(c, pos);
}
size_type find_last_not_of(const char *s, size_type pos = npos) const
{
return find_last_not_of(view_type(s), pos);
}
#endif
};
using _string_view = cstr<transfer_none_t>;
inline bool
operator==(_string_view x, _string_view y) noexcept
{
return x.compare(y) == 0;
}
inline bool
operator!=(_string_view x, _string_view y) noexcept
{
return !(x == y);
}
inline bool
operator<(_string_view x, _string_view y) noexcept
{
return x.compare(y) < 0;
}
inline bool
operator>(_string_view x, _string_view y) noexcept
{
return y < x;
}
inline bool
operator<=(_string_view x, _string_view y) noexcept
{
return !(y < x);
}
inline bool
operator>=(_string_view x, _string_view y) noexcept
{
return !(x < y);
}
#ifndef GI_NO_STRING_IOS
inline std::ostream &
operator<<(std::ostream &o, _string_view sv)
{
// backwards compatibility; behave similar to empty string
return o << (sv ? sv.c_str() : "");
}
#endif
// purpose of silly Delay is to avoid gcc premature instantiation of converter
// (in the hook constructor of base class with cstring as From ??)
template<typename Delay = void>
class cstring_d : public cstr<transfer_full_t>
{
using self_type = cstring_d;
using super_type = cstr<transfer_full_t>;
public:
using super_type::super_type;
// if all other construction fails, try to pass through string
template<typename... Args,
typename NoConvert = typename std::enable_if<
!std::is_constructible<super_type, Args...>::value>::type,
typename Enable = typename std::enable_if<
std::is_constructible<std::string, Args...>::value>::type>
cstring_d(Args &&...args)
: super_type(std::string(std::forward<Args>(args)...))
{}
constexpr pointer gobj_() { return data_; }
// re-use string
template<typename... Args>
self_type &assign(Args &&...t)
{
return *this = std::string().assign(std::forward<Args>(t)...);
}
// access
using super_type::at;
constexpr reference at(size_type i)
{
return i < size() ? data_[i]
: (try_throw(std::out_of_range("cstr::at")), data_[i]);
}
using super_type::front;
constexpr reference front() { return data_[0]; }
using super_type::back;
constexpr reference back() { return data_[size() - 1]; }
using super_type::data;
constexpr pointer data() noexcept { return data_; }
// iterators
using super_type::begin;
constexpr iterator begin() noexcept { return data_; }
using super_type::end;
constexpr iterator end() noexcept { return data_ ? data_ + size() : nullptr; }
using super_type::rbegin;
reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
using super_type::rend;
reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
// operations
using super_type::clear;
void push_back(char c)
{
char str[] = {c, 0};
self_type n{
g_strconcat(c_str() ? c_str() : "", str, (char *)NULL), transfer_full};
swap(n);
}
void pop_back()
{
auto s = size();
if (s)
at(s - 1) = 0;
}
self_type &append(const char *str)
{
self_type n{
g_strconcat(c_str() ? c_str() : "", str, (char *)NULL), transfer_full};
swap(n);
return *this;
}
template<typename Transfer>
self_type &append(const cstr<Transfer> &str)
{
return append(str.data());
}
// otherwise delegate
template<typename... Args>
self_type &append(Args &&...args)
{
std::string s;
s.append(std::forward<Args>(args)...);
// make sure to select the desired variant
return append((const char *)s.c_str());
}
// likewise for +=
self_type &operator+=(char c)
{
push_back(c);
return *this;
}
template<typename T>
self_type &operator+=(const T &o)
{
return append(o);
}
self_type substr(size_type pos = 0, size_type n = npos) const
{
auto l = size();
return (pos > l)
? (try_throw(std::out_of_range("cstr::substr")), self_type())
: self_type(data_ + pos, std::min(n, l - pos));
}
// indeed some are lacking/skipped
// only minimal compatibility
// custom; align with other cases
self_type copy_() { return substr(0); }
};
using cstring = cstring_d<>;
// likewise; (delayed) view type
template<typename Delay = void>
class cstring_v_d : public cstr<transfer_none_t>
{
using self_type = cstring_v_d;
using super_type = cstr<transfer_none_t>;
public:
using super_type::super_type;
// primary reason for subtype
// provide copy/upgrade to owning variant
cstring copy_() { return {g_strdup(this->data()), transfer_full}; }
};
using cstring_v = cstring_v_d<>;
inline cstring
operator+(const _string_view x, const _string_view y) noexcept
{
if (!x)
return {g_strdup(y.c_str()), transfer_full};
if (!y)
return {g_strdup(x.c_str()), transfer_full};
return {g_strconcat(x.c_str(), y.c_str(), (char *)NULL), transfer_full};
}
inline cstring
operator+(const _string_view x, char y) noexcept
{
if (!x)
return {1, y};
char str[] = {y, 0};
return x + str;
}
inline cstring
operator+(char x, const _string_view y) noexcept
{
if (!y)
return {1, x};
char str[] = {x, 0};
return str + y;
}
// add convenient conversions
// local helper traits used below
namespace trait
{
template<typename T, std::size_t SIZE, typename Enable = void>
struct has_data_member : std::false_type
{};
template<typename T, std::size_t SIZE>
struct has_data_member<T, SIZE,
typename std::enable_if<
std::is_pointer<decltype(std::declval<T>().c_str())>::value>::type>
: std::integral_constant<bool, sizeof(*std::declval<T>().c_str()) == SIZE>
{};
template<typename T, typename Enable = void>
struct has_size_member : std::false_type
{};
template<typename T>
struct has_size_member<T, typename std::enable_if<std::is_integral<
decltype(std::declval<T>().size())>::value>::type>
: std::true_type
{};
template<typename T>
struct is_string_type
: public std::integral_constant<bool,
has_data_member<T, 1>::value && has_size_member<T>::value>
{};
} // namespace trait
} // namespace detail
namespace convert
{
template<typename From>
struct converter<From, detail::cstr<transfer_full_t>,
typename std::enable_if<detail::trait::is_string_type<From>::value>::type>
: public converter_base<From, detail::cstr<transfer_full_t>>
{
static detail::cstring convert(const From &v)
{
return {(char *)v.c_str(), v.size()};
}
};
// to a typical string(_view) case
// (avoid conflict with above)
// the traits_type (tries to) restricts this to std::string(_view)
// not doing so might conveniently allow conversion to other types as well.
// however, this would also allow e.g. QByteArray, which comes with an operator+
// in global namespace (also selected by non-ADL lookup),
// which then results in ambiguous overload
// (with the operator+ that is provided above)
template<typename Transfer, typename To>
struct converter<detail::cstr<Transfer>, To,
typename std::enable_if<
!std::is_base_of<detail::String, To>::value &&
!std::is_same<typename To::traits_type, void>::value &&
std::is_constructible<To, const char *, size_t>::value>::type>
: public converter_base<detail::cstr<Transfer>, To>
{
static To convert(const detail::cstr<Transfer> &v)
{
return v.c_str() ? To{(char *)v.c_str(), v.size()} : To();
}
};
#if __cplusplus >= 201703L
// to an std::optional string(_view) case
template<typename Transfer, typename To>
struct converter<detail::cstr<Transfer>, To,
typename std::enable_if<std::is_constructible<To, std::in_place_t,
const char *, size_t>::value>::type>
: public converter_base<detail::cstr<Transfer>, To>
{
static To convert(const detail::cstr<Transfer> &v)
{
abort();
return v.c_str() ? To{std::in_place, (char *)v.c_str(), v.size()} : To();
}
};
#endif
} // namespace convert
using detail::cstring;
using detail::cstring_v;
// sanity check; match C counterpart
static_assert(sizeof(cstring) == sizeof(char *), "");
static_assert(sizeof(cstring_v) == sizeof(char *), "");
namespace traits
{
template<>
struct ctype<const gi::cstring, void>
{
typedef const char *type;
};
template<>
struct ctype<gi::cstring, void>
{
typedef char *type;
};
template<>
struct ctype<const gi::cstring_v, void>
{
typedef const char *type;
};
template<>
struct ctype<gi::cstring_v, void>
{
typedef char *type;
};
template<>
struct cpptype<char *, transfer_full_t>
{
using type = gi::cstring;
};
template<>
struct cpptype<char *, transfer_none_t>
{
using type = gi::cstring_v;
};
} // namespace traits
} // namespace gi
// specialize std::hash for suitable use
GI_MODULE_EXPORT
namespace std
{
template<>
struct hash<gi::cstring>
{
typedef gi::cstring argument_type;
typedef std::size_t result_type;
result_type operator()(argument_type const &s) const
{
return s.c_str() ? g_str_hash(s.c_str()) : 0;
}
};
template<>
struct hash<gi::cstring_v>
{
typedef gi::cstring_v argument_type;
typedef std::size_t result_type;
result_type operator()(argument_type const &s) const
{
return s.c_str() ? g_str_hash(s.c_str()) : 0;
}
};
} // namespace std
#endif // GI_STRING_HPP

545
cmake/external/glib/cppgir/gi/value.hpp vendored Normal file
View File

@@ -0,0 +1,545 @@
#ifndef GI_VALUE_HPP
#define GI_VALUE_HPP
#include "exception.hpp"
#include "wrap.hpp"
GI_MODULE_EXPORT
namespace gi
{
namespace repository
{
// specialize to declare gtype info
// if not within class get_type()
// gvalue info can also be included this way
// to inject support into Value wrapper
template<typename T>
struct declare_gtype_of : public std::false_type
{};
} // namespace repository
namespace traits
{
namespace detail
{
template<typename T, class Enable = void>
struct gtype : public std::false_type
{
static GType get_type()
{
// dummy test to trigger (almost) always
static_assert(std::is_void<T>::value, "type is not a registered GType");
return 0;
}
};
// gboxed/gobject (or otherwise class) cases
template<typename T>
struct gtype<T, typename if_valid_type<decltype(T::get_type_())>::type>
: public std::true_type
{
typedef typename std::remove_reference<T>::type CppType;
static GType get_type() { return CppType::get_type_(); }
};
// otherwise externally declared
template<typename T>
struct gtype<T, typename if_valid_type<decltype(repository::declare_gtype_of<
T>::get_type())>::type> : public std::true_type
{
static constexpr GType (
*get_type)() = repository::declare_gtype_of<T>::get_type;
};
// gvalue helper info
template<typename T, class Enable = void>
struct gvalue : public std::false_type
{};
// as declared (both of set_value and get_value or neither)
template<typename T>
struct gvalue<T,
typename if_valid_type<decltype(repository::declare_gtype_of<T>::get_value(
nullptr))>::type> : public std::true_type
{
static T get(const GValue *val)
{
return repository::declare_gtype_of<T>::get_value(val);
}
static void set(GValue *val, T t)
{
repository::declare_gtype_of<T>::set_value(val, t);
}
};
template<typename T>
using is_enum_or_bitfield =
typename std::conditional<std::is_enum<T>::value && gtype<T>::value,
std::true_type, std::false_type>::type;
// handle enum/flags cases, rather than many declares
template<typename T>
struct gvalue<T, typename std::enable_if<is_enum_or_bitfield<T>::value>::type>
: public std::true_type
{
static T get(const GValue *val)
{
GType t = gtype<T>::get_type();
if (G_TYPE_IS_FLAGS(t))
return static_cast<T>(g_value_get_flags(val));
// assume enum, let glib complain otherwise
return static_cast<T>(g_value_get_enum(val));
}
static void set(GValue *val, T v)
{
GType t = gtype<T>::get_type();
if (G_TYPE_IS_FLAGS(t)) {
g_value_set_flags(val, (guint)v);
} else {
// assume enum, let glib complain otherwise
g_value_set_enum(val, (gint)v);
}
}
};
} // namespace detail
template<typename T>
using gtype = detail::gtype<
typename std::decay<typename std::remove_reference<T>::type>::type>;
template<typename T>
using gvalue = detail::gvalue<
typename std::decay<typename std::remove_reference<T>::type>::type>;
template<typename T>
using is_enum_or_bitfield = detail::is_enum_or_bitfield<
typename std::decay<typename std::remove_reference<T>::type>::type>;
#if 0
template<typename T, typename Enable = void>
struct is_flag : public std::false_type {};
// TODO extend fundamental type stuff ??
template<typename T>
struct is_flag<T,
typename std::enable_if<std::is_enum<T>::value &&
repository::declare_gtype_of<T>::get_fundamental_type() == G_TYPE_FLAGS>::type> :
public std::true_type {};
#endif
} // namespace traits
// C++ types are used below (e.g. int) instead of e.g. gint
// since gint64 might map to same as glong (or not)
// so some of the int types are "best approximation" from C++ type to GType
// instead; use the following types to guide to the right overload
typedef char vchar;
typedef long vlong;
typedef int vint;
typedef long long vint64;
typedef bool vboolean;
typedef unsigned char vuchar;
typedef unsigned long vulong;
typedef unsigned int vuint;
typedef unsigned long long vuint64;
typedef float vfloat;
typedef double vdouble;
// NOTE: (plain) char might be signed or unsigned depending on platform
// but gchar == char always anyway
static_assert(std::is_same<gchar, char>::value, "now what");
namespace repository
{
template<>
struct declare_gtype_of<void>
{
static GType get_type() { return G_TYPE_NONE; }
};
#define GI_DECLARE_GTYPE(cpptype, g_type) \
template<> \
struct declare_gtype_of<cpptype> \
{ \
static constexpr GType get_type() { return g_type; } \
};
#define GI_DECLARE_GTYPE_VALUE(cpptype, g_type, value_suffix) \
template<> \
struct declare_gtype_of<cpptype> \
{ \
static constexpr GType get_type() { return g_type; } \
static void set_value(GValue *val, cpptype v) \
{ \
g_value_set_##value_suffix(val, v); \
} \
static cpptype get_value(const GValue *val) \
{ \
return g_value_get_##value_suffix(val); \
} \
};
// declare non-cv qualified type
GI_DECLARE_GTYPE_VALUE(gpointer, G_TYPE_POINTER, pointer)
GI_DECLARE_GTYPE_VALUE(bool, G_TYPE_BOOLEAN, boolean)
GI_DECLARE_GTYPE_VALUE(char, G_TYPE_CHAR, schar)
GI_DECLARE_GTYPE_VALUE(unsigned char, G_TYPE_UCHAR, uchar)
GI_DECLARE_GTYPE_VALUE(int, G_TYPE_INT, int)
GI_DECLARE_GTYPE_VALUE(unsigned int, G_TYPE_UINT, uint)
GI_DECLARE_GTYPE_VALUE(long, G_TYPE_LONG, long)
GI_DECLARE_GTYPE_VALUE(unsigned long, G_TYPE_ULONG, ulong)
GI_DECLARE_GTYPE_VALUE(long long, G_TYPE_INT64, int64)
GI_DECLARE_GTYPE_VALUE(unsigned long long, G_TYPE_UINT64, uint64)
GI_DECLARE_GTYPE_VALUE(float, G_TYPE_FLOAT, float)
GI_DECLARE_GTYPE_VALUE(double, G_TYPE_DOUBLE, double)
// some custom set/get for these
// remember; the pointer is non-const
GI_DECLARE_GTYPE(const char *, G_TYPE_STRING)
GI_DECLARE_GTYPE(char *, G_TYPE_STRING)
GI_DECLARE_GTYPE(std::string, G_TYPE_STRING)
GI_DECLARE_GTYPE(gi::cstring, G_TYPE_STRING)
GI_DECLARE_GTYPE(gi::cstring_v, G_TYPE_STRING)
#undef GI_DECLARE_GTYPE_VALUE
#undef GI_DECLARE_GTYPE
} // namespace repository
namespace detail
{
// GValue helpers
// set_value
template<typename T,
typename std::enable_if<traits::gvalue<T>::value>::type * = nullptr>
inline void
set_value(GValue *val, T v)
{
traits::gvalue<T>::set(val, v);
}
inline void
set_value(GValue *val, const std::string &s)
{
g_value_set_string(val, s.c_str());
}
inline void
set_value(GValue *val, gi::cstring_v s)
{
g_value_set_string(val, s.c_str());
}
inline void
set_value(GValue *val, const char *s)
{
g_value_set_string(val, s);
}
template<typename T,
typename std::enable_if<traits::is_object<T>::value>::type * = nullptr>
inline void
set_value(GValue *val, T v)
{
// set might not handle NULL case
g_value_take_object(val, v.gobj_copy_());
}
template<typename T,
typename std::enable_if<traits::is_gboxed<T>::value>::type * = nullptr>
inline void
set_value(GValue *val, T v)
{
// set might not handle NULL case
g_value_take_boxed(val, v.gobj_copy_());
}
// container case
template<typename T, typename T::_detail::DataType * = nullptr>
inline void
set_value(GValue *val, T v)
{
v._set_value(val);
}
// get_value
template<typename T,
typename std::enable_if<traits::is_object<T>::value>::type * = nullptr>
inline T
get_value(const GValue *val)
{
// ensure sanity
static_assert(std::is_class<T>::value && !std::is_const<T>::value,
"non cv-qualified class type required");
auto cv =
static_cast<typename traits::ctype<T>::type>(g_value_dup_object(val));
if (cv && !g_type_is_a(G_OBJECT_TYPE(cv), traits::gtype<T>::get_type()))
detail::try_throw(transform_error(G_OBJECT_TYPE(cv)));
return gi::wrap(cv, transfer_full);
}
template<typename T,
typename std::enable_if<traits::is_gboxed<T>::value &&
!traits::is_reftype<T>::value>::type * = nullptr>
inline T
get_value(const GValue *val)
{
// no way to know whether boxed type is correct
// ensure sanity
static_assert(std::is_class<T>::value && !std::is_const<T>::value,
"non cv-qualified class type required");
auto cv =
static_cast<typename traits::ctype<T>::type>(g_value_dup_boxed(val));
return gi::wrap(cv, transfer_full);
}
template<typename T,
typename std::enable_if<traits::is_gboxed<T>::value &&
traits::is_reftype<T>::value>::type * = nullptr>
inline T
get_value(const GValue *val)
{
// no way to know whether boxed type is correct
// ensure sanity
static_assert(std::is_class<T>::value && !std::is_const<T>::value,
"non cv-qualified class type required");
auto cv =
static_cast<typename traits::ctype<T>::type>(g_value_get_boxed(val));
return gi::wrap(cv, transfer_none);
}
template<typename T,
typename std::enable_if<traits::gvalue<T>::value>::type * = nullptr>
inline T
get_value(const GValue *val)
{
return traits::gvalue<T>::get(val);
}
// sigh ...
template<typename T,
typename std::enable_if<std::is_same<T, std::string>::value ||
std::is_base_of<detail::String, T>::value>::type * =
nullptr>
inline T
get_value(const GValue *val)
{
return gi::wrap(g_value_get_string(val), transfer_none);
}
// container case
template<typename T, typename T::_detail::DataType * = nullptr>
inline T
get_value(const GValue *val)
{
static_assert(traits::is_decayed<T>::value, "");
return T::template _get_value<T>(val);
}
// convenience helper ...
template<typename T,
typename std::enable_if<std::is_same<T, void>::value>::type * = nullptr>
inline T
get_value(const GValue * /*val*/)
{}
// simple (RAII) Value wrapper for (internal) use
struct Value : public GValue, noncopyable
{
void clear() { memset((void *)this, 0, sizeof(*this)); }
Value() { clear(); }
template<typename T, typename Enable = disable_if_same_or_derived<T, Value>>
explicit Value(T &&v)
{
clear();
g_value_init(this, traits::gtype<T>::get_type());
set_value(this, std::forward<T>(v));
}
template<typename T>
void init()
{
// handle no-op void (return value) corner case
const GType tp = traits::gtype<T>::get_type();
if (tp != G_TYPE_NONE)
g_value_init(this, tp);
}
// let's not copy, but ok to move around
Value(Value &&other)
{
memcpy((void *)this, &other, sizeof(*this));
other.clear();
}
Value &operator=(Value &&other)
{
if (this != &other) {
memcpy((void *)this, &other, sizeof(*this));
other.clear();
}
return *this;
}
~Value()
{
if (G_VALUE_TYPE(this))
g_value_unset(this);
}
};
// we really rely upon this as part of the ABI
// justifies operations above and some explicit casts above
// (to avoid -Wclass-memaccess)
static_assert(sizeof(Value) == sizeof(GValue), "unsupported compiler");
template<typename R>
inline R
transform_value(const GValue *val)
{
detail::Value dest;
dest.init<R>();
if (!g_value_transform(val, &dest))
detail::try_throw(detail::transform_error(G_VALUE_TYPE(&dest)));
return detail::get_value<R>(&dest);
}
// hand-crafted Value wrapper with an interface as it would be generated
// and used by generated code, along with additional convenience
class ValueBase : public gi::detail::GBoxedWrapperBase<ValueBase, GValue>
{
using self_type = ValueBase;
public:
static GType get_type_() G_GNUC_CONST { return G_TYPE_VALUE; }
void copy(self_type dest) const { g_value_copy(gobj_(), dest.gobj_()); }
void reset() { g_value_reset(gobj_()); }
void unset() { g_value_unset(gobj_()); }
bool transform(self_type dest) const
{
return g_value_transform(gobj_(), dest.gobj_());
}
static bool type_compatible(GType src_type, GType dest_type)
{
return g_value_type_compatible(src_type, dest_type);
}
static bool type_transformable(GType src_type, GType dest_type)
{
return g_value_type_transformable(src_type, dest_type);
}
template<typename T>
self_type &set_value(T &&v)
{
detail::set_value(gobj_(), std::forward<T>(v));
return *this;
}
template<typename T>
T get_value() const
{
return detail::get_value<T>(gobj_());
}
template<typename R>
R transform_value()
{
return detail::transform_value<R>(gobj_());
}
};
} // namespace detail
namespace repository
{
namespace GObject
{
// build on above base with additional convenience (in owning case)
class Value_Ref;
class Value : public gi::detail::GBoxedWrapper<Value, GValue, detail::ValueBase,
Value_Ref>
{
typedef gi::detail::GBoxedWrapper<Value, GValue, detail::ValueBase, Value_Ref>
super_type;
typedef Value self_type;
public:
using detail::ValueBase::copy;
using super_type::copy;
// hybrid GBoxed/CBoxed
void allocate_()
{
if (this->data_)
return;
// make sure we match GValue boxed allocation with boxed free
// (though last kown implementation uses g_new0/g_free)
detail::Value tmp;
this->data_ = (::GValue *)g_boxed_copy(G_TYPE_VALUE, &tmp);
}
// convenience
Value() { allocate_(); }
// allow non-explicit use for convenient calling
// but avoid copy/move construct use
template<typename T,
typename std::enable_if<!std::is_base_of<Value,
typename std::remove_reference<T>::type>::value>::type * = nullptr>
Value(T &&t)
{
allocate_();
init<T>(std::forward<T>(t));
}
Value &init(GType tp)
{
// no-op void corner case
if (tp != G_TYPE_NONE)
g_value_init(gobj_(), tp);
return *this;
}
template<typename T>
Value &init(T &&v)
{
g_value_init(gobj_(), traits::gtype<T>::get_type());
set_value(std::forward<T>(v));
return *this;
}
};
class Value_Ref
: public gi::detail::GBoxedRefWrapper<Value, ::GValue, detail::ValueBase>
{
typedef gi::detail::GBoxedRefWrapper<Value, ::GValue, detail::ValueBase>
super_type;
using super_type::super_type;
};
} // namespace GObject
template<>
struct declare_cpptype_of<GValue>
{
typedef GObject::Value type;
};
} // namespace repository
} // namespace gi
#endif // GI_VALUE_HPP

369
cmake/external/glib/cppgir/gi/wrap.hpp vendored Normal file
View File

@@ -0,0 +1,369 @@
#ifndef GI_WRAP_HPP
#define GI_WRAP_HPP
#include "base.hpp"
#include "string.hpp"
GI_MODULE_EXPORT
namespace gi
{
// object/wrapper conversion
template<typename CType, typename TransferType,
typename CppType = typename traits::cpptype<CType *>::type,
typename Enable =
typename std::enable_if<traits::is_wrapper<CppType>::value>::type>
inline typename std::remove_const<CppType>::type
wrap(CType *v, const TransferType &t)
{
// should be called with a concrete transfer subtype
static_assert(!std::is_same<TransferType, transfer_t>::value, "");
// the class wrap only has to deal with non-const class type
typedef typename std::remove_const<CppType>::type TNC;
return CppType::template wrap<TNC>(v, t.value);
}
// special case; wrap an owned box with full transfer
template<typename CType,
typename CppType = typename traits::cpptype<CType *>::type,
typename Enable =
typename std::enable_if<traits::is_boxed<CppType>::value>::type,
typename TNC = typename std::remove_const<CppType>::type>
inline TNC
wrap(CType *v, const transfer_full_t &)
{
return CppType::template wrap<TNC>(v);
}
// special case; wrap a unowned box (that is, transfer none) to the Ref type
template<typename CType,
typename CppType = typename traits::cpptype<CType *>::type,
typename Enable =
typename std::enable_if<traits::is_boxed<CppType>::value>::type,
typename RefType = typename traits::reftype<
typename std::remove_const<CppType>::type>::type>
inline RefType
wrap(CType *v, const transfer_none_t &)
{
// unowned and no copy in all cases
return RefType::template wrap<RefType>(v);
}
template<typename T,
typename std::remove_reference<T>::type::BaseObjectType * = nullptr>
inline typename traits::ctype<T>::type
unwrap(T &&v, const transfer_none_t &)
{
using DT = typename std::decay<T>::type;
// test convenience
#ifndef GI_TEST
static constexpr bool ALLOW_ALL = false;
#else
static constexpr bool ALLOW_ALL = true;
#endif
static_assert(ALLOW_ALL || traits::is_wrapper<DT>::value ||
traits::is_reftype<DT>::value,
"transfer none expects refcnt wrapper or reftype (not owning box)");
return v.gobj_();
}
namespace detail
{
// lvalue
template<typename T,
typename std::enable_if<!traits::is_boxed<T>::value>::type * = nullptr>
inline typename traits::ctype<T>::type
unwrap(const T &v, const transfer_full_t &, std::true_type)
{
// no implicit copy for boxed; should end up in other case
static_assert(!traits::is_boxed<T>::value, "boxed copy");
return v.gobj_copy_();
}
// rvalue
template<typename T,
typename std::enable_if<!traits::is_reftype<T>::value>::type * = nullptr>
inline typename traits::ctype<T>::type
unwrap(T &&v, const transfer_full_t &, std::false_type)
{
// in case of wrapper/object;
// release only provided on base case with void* return
return (typename traits::ctype<T>::type)v.release_();
}
} // namespace detail
template<typename T, typename std::decay<T>::type::BaseObjectType * = nullptr>
inline typename traits::ctype<T>::type
unwrap(T &&v, const transfer_full_t &t)
{
// universal reference dispatch
return detail::unwrap(std::forward<T>(v), t, std::is_lvalue_reference<T>());
}
// container types
template<typename T, typename Transfer,
typename std::decay<T>::type::_detail::DataType * = nullptr>
inline typename std::decay<T>::type::_detail::DataType
unwrap(T &&v, const Transfer &t)
{
// universal reference dispatch
return std::forward<T>(v)._unwrap(t);
}
// to wrap a container, the target wrapped type needs to be explicitly specified
// (in particular the contained element type)
// (target type should be decay'ed type)
// generic case, let wrap take care of it (usually no target type is needed)
template<typename TargetType, typename CType, typename Transfer,
decltype(wrap(std::declval<typename std::decay<CType>::type>(),
Transfer())) * = nullptr>
TargetType
wrap_to(CType v, const Transfer &t)
{
static_assert(traits::is_decayed<TargetType>::value, "");
return wrap(v, t);
}
// container case
template<typename TargetType, typename CType, typename Transfer,
typename TargetType::_detail::DataType * = nullptr>
TargetType
wrap_to(CType v, const Transfer &t)
{
static_assert(traits::is_decayed<TargetType>::value, "");
return TargetType::template _wrap<TargetType>(v, t);
}
// container size case
template<typename TargetType, typename CType, typename Transfer,
typename TargetType::_detail::DataType * = nullptr>
TargetType
wrap_to(CType v, int s, const Transfer &t)
{
static_assert(traits::is_decayed<TargetType>::value, "");
return TargetType::template _wrap<TargetType>(v, s, t);
}
#if 0
// string conversion
inline std::string
wrap(const char *v, const transfer_none_t &,
const direction_t & = direction_dummy)
{
return detail::make_string(v);
}
// actually should not accept const input (as it makes no sense for full
// transfer) but let's go the runtime way and not mind that too much (code
// generation will warn though)
inline std::string
wrap(const char *v, const transfer_full_t &,
const direction_t & = direction_dummy)
{
// a custom type that would allow direct mem transfer might be nice
// but that might be too nifty and create yet-another-string-type
std::string s;
if (v) {
s = v;
g_free((char *)v);
}
return s;
}
#else
// string conversion
inline gi::cstring_v
wrap(const char *v, const transfer_none_t &)
{
return cstring_v(v);
}
// actually should not accept const input (as it makes no sense for full
// transfer) but let's go the runtime way and not mind that too much (code
// generation will warn though)
inline gi::cstring
wrap(const char *v, const transfer_full_t &)
{
// as said, never mind const
return cstring{(char *)v, transfer_full};
}
#endif
// return const here, as somewhat customary, also
// wrapped function call is force-casted anyway (to const char* parameter)
// FIXME ?? though const is generally rare and it breaks consistency that way
inline const gchar *
unwrap(const std::string &v, const transfer_none_t &)
{
return v.c_str();
}
inline const gchar *
unwrap(const detail::optional_string &v, const transfer_none_t &)
{
return v.empty() ? nullptr : v.c_str();
}
template<typename Transfer>
inline const gchar *
unwrap(const detail::cstr<Transfer> &v, const transfer_none_t &)
{
return v.c_str();
}
// R-value variants of the above, akin to transfer_none from an owning R-value
// bad dangling things would happen
inline const gchar *unwrap(std::string &&v, const transfer_none_t &) = delete;
inline const gchar *unwrap(
detail::optional_string &&v, const transfer_none_t &) = delete;
template<typename Transfer>
inline const gchar *
unwrap(detail::cstr<Transfer> &&v, const transfer_none_t &)
{
static_assert(std::is_same<Transfer, transfer_none_t>::value,
"transfer none expects non-owning type");
return v.c_str();
}
inline gchar *
unwrap(const std::string &v, const transfer_full_t &)
{
return g_strdup(v.c_str());
}
inline gchar *
unwrap(const detail::optional_string &v, const transfer_full_t &)
{
return v.empty() ? nullptr : g_strdup(v.c_str());
}
template<typename Transfer>
inline gchar *
unwrap(const gi::detail::cstr<Transfer> &v, const transfer_full_t &)
{
return g_strdup(v.c_str());
}
inline gchar *
unwrap(gi::cstring &&v, const transfer_full_t &)
{
return v.release_();
}
// enum conversion
template<typename T,
typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
inline typename traits::cpptype<T>::type
wrap(T v, const transfer_t & = transfer_dummy)
{
return (typename traits::cpptype<T>::type)v;
}
template<typename T,
typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
inline typename traits::ctype<T>::type
unwrap(T v, const transfer_t & = transfer_dummy)
{
return (typename traits::ctype<T>::type)v;
}
// plain basic pass along
template<typename T,
typename std::enable_if<traits::is_basic<T>::value>::type * = nullptr>
inline T
wrap(T v, const transfer_t & = transfer_dummy)
{
return v;
}
template<typename T,
typename std::enable_if<traits::is_basic<T>::value>::type * = nullptr>
inline T
unwrap(T v, const transfer_t & = transfer_dummy)
{
return v;
}
// callback conversion
// async or destroy-notify;
// signature forces copy, and std::move is used in unwrap call
template<typename T,
typename std::remove_reference<T>::type::CallbackWrapperType * = nullptr>
inline typename std::remove_reference<T>::type::CallbackWrapperType
unwrap(T &&v, const transfer_t & = transfer_dummy)
{
return typename std::remove_reference<T>::type::CallbackWrapperType(
std::forward<T>(v));
}
// call or destroy-notify scope
template<typename T>
inline typename std::remove_reference<T>::type::template wrapper_type<false> *
unwrap(T &&v, const scope_t &)
{
return new
typename std::remove_reference<T>::type::template wrapper_type<false>(
std::forward<T>(v));
}
// async scope
template<typename T>
inline typename std::remove_reference<T>::type::template wrapper_type<true> *
unwrap(T &&v, const scope_async_t &)
{
return new
typename std::remove_reference<T>::type::template wrapper_type<true>(
std::forward<T>(v));
}
// dynamic GType casting within GObject/interface hierarchy
template<typename T, typename I,
typename std::enable_if<
traits::is_object<T>::value &&
traits::is_object<typename std::decay<I>::type>::value>::type * =
nullptr>
inline T
object_cast(I &&t)
{
if (!t || !g_type_is_a(t.gobj_type_(), T::get_type_())) {
return T();
} else {
return wrap((typename T::BaseObjectType *)t.gobj_copy_(), transfer_full);
}
}
// this utility can be used to arrange for pointer-like const-ness
// that is, a const shared_ptr<T> is still usable like (non-const) T*
// in a way, any wrapper object T acts much like a smart-pointer,
// but as the (code generated) methods are non-const, they are not usable
// if the wrapper object is const (e.g. captured in a lambda)
// this helper object/class can be wrapped around the wrapper (phew)
// to absorb/shield the (outer) `const` (as it behaves as other smart pointers)
template<typename T>
class cs_ptr
{
T t;
public:
// rough check; T is expected to be a pointer wrapper
static_assert(sizeof(T) == sizeof(void *), "");
// if T not copy-able, argument may need to be move'd
cs_ptr(T _t) : t(std::move(_t)) {}
operator T() const & { return t; }
operator T() && { return std::move(t); }
T *get() const { return &t; }
// C++ sacrilege,
// but the const of pointer in T does not extend to pointee anyway
T *operator*() const { return const_cast<T *>(&t); }
T *operator->() const { return const_cast<T *>(&t); }
};
} // namespace gi
#endif // GI_WRAP_HPP

194
cmake/external/glib/cppgir/meson.build vendored Normal file
View File

@@ -0,0 +1,194 @@
project('cppgir',
['c', 'cpp'],
meson_version : '>= 0.61',
version : '2.0.0',
default_options : [
'warning_level=2',
'cpp_std=c++17'
]
)
message('meson system only considers generator and includes',
'\n\tsee CMake build for full build including examples')
compiler = meson.get_compiler('cpp')
foreach arg : ['-Wnon-virtual-dtor']
if compiler.has_argument(arg)
add_project_arguments(arg, language: 'cpp')
endif
endforeach
# generator binary
# dependencies
boost_dep = dependency('boost', version : '>=1.58', required : true)
# fmtlib
fmtlib_dep = dependency('fmt', required : false)
if not fmtlib_dep.found()
# fallback for old version without pkg-config
fmtlib_dep = compiler.find_library('fmt', has_headers : ['fmt/format.h'], required : false)
endif
# check C++20 format
has_format = compiler.compiles(files('cmake/cpp20_format.cpp'),
args : ['-std=c++20'],
name : 'std::format check'
)
# check and decide on which fmt to use
use_fmtlib = 'none'
build_fmt = get_option('build_fmt')
if build_fmt == 'auto'
if fmtlib_dep.found()
use_fmtlib = '1'
elif has_format
use_fmtlib = '0'
endif
elif build_fmt == 'fmtlib' and fmtlib_dep.found()
use_fmtlib = '1'
elif build_fmt == 'stdformat' and has_format
use_fmtlib = '0'
endif
if use_fmtlib == 'none'
error('no format library found')
endif
# required ignore file
fs = import('fs')
gi_ignore_file_dir = 'data'
gi_ignore_file = 'cppgir.ignore'
cppgir_ignore = fs.read('data/cppgir.ignore')
if host_machine.system() == 'windows'
gi_ignore_file_platform = 'cppgir_win.ignore'
cppgir_win_ignore = fs.read('data/cppgir_win.ignore')
cppgir_unix_ignore = ''
else
gi_ignore_file_platform = 'cppgir_unix.ignore'
cppgir_unix_ignore = fs.read('data/cppgir_unix.ignore')
cppgir_win_ignore = ''
endif
gi_install_full_datadir = \
'@0@/@1@'.format(get_option('prefix'), get_option('datadir'))
gi_ignore_file_install_dir = \
'@0@/@1@'.format(gi_install_full_datadir, meson.project_name())
cppgir_sources = [
'tools/cppgir.cpp',
'tools/genbase.cpp', 'tools/genbase.hpp',
'tools/genns.cpp', 'tools/genns.hpp',
'tools/genutils.cpp', 'tools/genutils.hpp',
'tools/function.cpp', 'tools/function.hpp',
'tools/repository.cpp', 'tools/repository.hpp',
'tools/common.hpp'
]
cppgir_deps = [boost_dep]
cppgir_overrides = []
cppgir_args = []
# adjust to options and situation
if use_fmtlib.to_int() > 0
cppgir_deps += [fmtlib_dep]
else
cppgir_overrides = ['cpp_std=c++20']
endif
if get_option('build_embed_ignore')
# generate embedded ignore data
conf_data = configuration_data()
conf_data.set('CPPGIR_IGNORE', cppgir_ignore)
conf_data.set('CPPGIR_UNIX_IGNORE', cppgir_unix_ignore)
conf_data.set('CPPGIR_WIN_IGNORE', cppgir_win_ignore)
configure_file(configuration : conf_data,
input : 'tools/ignore.hpp.in', output : 'ignore.hpp')
else
install_data(gi_ignore_file_dir / gi_ignore_file,
gi_ignore_file_dir / gi_ignore_file_platform,
install_dir : gi_ignore_file_install_dir)
cppgir_args += \
[f'-DDEFAULT_IGNORE_FILE=@gi_ignore_file_install_dir@/@gi_ignore_file@:@gi_ignore_file_install_dir@/@gi_ignore_file_platform@']
endif
# gir search path
if host_machine.system() != 'windows'
# add fixed fallback search places
cppgir_args += [
f'-DGI_DATA_DIR=@gi_install_full_datadir@/gir-1.0',
]
gir_dir = get_option('gir_dir')
if gir_dir != ''
cppgir_args += [f'-DGI_GIR_DIR=@gir_dir@']
endif
gir_default_dirs = get_option('gir_default_dirs')
if gir_default_dirs != ''
cppgir_args += [f'-DDEFAULT_GIRPATH=@gir_default_dirs@']
endif
endif
if get_option('link_stdfs')
# some older gcc might sometimes (?) need this, even in c++17 mode
# see issue #80
fs_dep = compiler.find_library('stdc++fs')
cppgir_deps += [fs_dep]
endif
cppgir = executable('cppgir',
cppgir_sources,
cpp_args : cppgir_args,
dependencies : cppgir_deps,
install : true,
override_options : cppgir_overrides
)
meson.override_find_program('cppgir', cppgir)
# gi headers
expected_code = '''#include <expected>
auto f() -> std::expected<int, int> { return 2; }
'''
has_expected = compiler.compiles(expected_code, name : 'std::expected check')
pkgconfig = import('pkgconfig')
pkgconfig.generate(name: 'cppgir',
version : meson.project_version(),
subdirs : ['cppgir', 'cppgir' / 'override'],
description : 'GObject Introspection C++ wrapper generator.'
)
install_subdir('gi', install_dir : 'include' / 'cppgir')
install_subdir('override', install_dir : 'include' / 'cppgir')
inc = include_directories('.', 'override')
if not has_expected
expected_lite_include = 'expected-lite' / 'include'
if not fs.exists(expected_lite_include / 'nonstd' / 'expected.hpp')
error('missing submodule expected-lite')
endif
# Add an include option and copy the directory is all we have to do.
# cppgir will automatically switch to `nonstd/expected.hpp` if it exists.
inc = [inc] + include_directories(expected_lite_include)
install_subdir(expected_lite_include / 'nonstd',
install_dir : 'include' / 'cppgir' / 'gi')
endif
cppgir_dep = declare_dependency(include_directories: inc)
meson.override_dependency('cppgir', cppgir_dep)
# manpage
ronn = find_program('ronn', native : true, required : false)
if not ronn.found()
message('ronn manpage processor not found; not building manpage')
elif get_option('build_doc')
message('building manpage')
manpage = 'cppgir.1'
custom_target(manpage, command : [ronn, '--roff', '--pipe', '@INPUT@'],
capture : true, input : ['docs/cppgir.md'], output : manpage,
install : true, install_dir : join_paths(get_option('mandir'), 'man1'))
endif
# documentation, including examples
docdir = get_option('datadir') / 'doc' / 'cppgir'
install_data('README.md', 'docs/cppgir.md', install_dir : docdir)
install_subdir('examples', install_dir : docdir)

View File

@@ -0,0 +1,31 @@
# Build option
option('build_fmt', type : 'combo',
choices : ['auto', 'fmtlib', 'stdformat'] ,
value : 'auto',
description : 'format library'
)
option('build_embed_ignore', type : 'boolean',
value : false,
description : 'embed default ignore'
)
option('gir_dir', type : 'string',
description : 'extra GIR search directory'
)
option('gir_default_dirs', type : 'string',
description : 'fallback GIR search prefix paths (to be suffixed with gir-1.0)',
# best left as-is to follow standard GIR search
value : '/usr/local/share:/usr/share'
)
option('link_stdfs', type : 'boolean',
value : false,
description : 'link to stdc++fs'
)
option('build_doc', type : 'boolean',
value : true,
description : 'build documentation'
)

Some files were not shown because too many files have changed in this diff Show More