Skip to content

Instantly share code, notes, and snippets.

@N-Dekker
Created July 4, 2018 13:50
Show Gist options
  • Save N-Dekker/210f162de64fae62cfb5f15746caf416 to your computer and use it in GitHub Desktop.
Save N-Dekker/210f162de64fae62cfb5f15746caf416 to your computer and use it in GitHub Desktop.
Proposed class to ease creating MeVisLab fields
/*=========================================================================
*
* Copyright Niels Dekker, LKEB, Leiden University Medical Center
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*=========================================================================*/
#include <mlFieldContainer.h>
#include <mlFields.h> // For TypedEnumField.
#include <memory> // For unique_ptr
#include <string>
#include <type_traits> // For remove_const_t
ML_START_NAMESPACE
// FieldCreator: class to ease creating MeVisLab fields, by calling its static
// function, FieldCreator::create(container, name, initialValue, fieldFunction).
class FieldCreator
{
private:
// Proxy to a field that is created when the proxy is converted (either
// implicitly or explicitly) to a field reference.
template <
typename InitialValueType,
typename FieldFunctionType>
class FieldProxy
{
public:
// Data members:
// Reference to the field container (typically an ml::Module).
FieldContainer& container;
// The name of the field to be created.
std::string name;
// The initial value to be set to the field.
// Note: No value is set when InitialValueType == std::nullptr_t.
InitialValueType initialValue;
// The function to be called just before the field is added to the container.
// Must accept a reference to the field as argument.
// Note: No attempt is made to call the function when FieldFunctionType == std::nullptr_t.
FieldFunctionType fieldFunction;
// Conversion operator. Creates a field, sets its value (if any), applies
// the fieldFunction (if any), and adds the field to the container.
template <typename FieldType>
operator FieldType&()
{
auto ptr = std::make_unique<std::remove_const_t<FieldType>>(name);
auto& result = *ptr;
FieldProxy::SetValue(result, initialValue);
FieldProxy::CallFunction(fieldFunction, result);
container.addField(ptr.release());
return result;
}
private:
// Private static helper functions:
template <typename FieldType, typename ValueType>
static void SetValue(FieldType& field, const ValueType& value)
{
// Ensure at compile-time that no narrowing conversion takes place.
using ExpectedValueType = decltype(std::declval<FieldType>().getValue());
field.setValue(ExpectedValueType{ value });
}
// Overload for the case when no initial value is specified.
template <typename FieldType>
static void SetValue(FieldType&, const std::nullptr_t&)
{
// Do nothing.
}
template <typename FunctionType, typename FieldType>
static void CallFunction(FunctionType&& fieldFunction, FieldType& field)
{
fieldFunction(field);
}
// Overload for the case when no function is specified.
template <typename FieldType>
static void CallFunction(std::nullptr_t, FieldType&)
{
// Do nothing.
}
};
public:
// Creates a field with the specified name, sets its initial value, applies
// the specified function to the field, and adds the field to the container.
// When InitialValueType is std::nullptr_t, initialValue is ignored.
// Also, when FieldFunctionType is std::nullptr_t, fieldFunction is ignored.
template <
typename InitialValueType = std::nullptr_t,
typename FieldFunctionType = std::nullptr_t>
static FieldProxy<InitialValueType, FieldFunctionType> create(
FieldContainer& container,
std::string name,
const InitialValueType initialValue = nullptr,
FieldFunctionType fieldFunction = nullptr)
{
return{ container, std::move(name), initialValue, std::move(fieldFunction) };
}
// Creates a field with the specified name, sets its enum values and its
// initial enum value, and adds the field to the container.
template <typename EnumType>
static TypedEnumField<EnumType>& create(
FieldContainer& container,
const std::string& name,
const EnumValues<EnumType>& enumValues,
const EnumType initialValue)
{
auto ptr = std::make_unique<TypedEnumField<EnumType>>(name, enumValues, initialValue);
auto& result = *ptr;
container.addField(ptr.release());
return result;
}
};
ML_END_NAMESPACE
@N-Dekker
Copy link
Author

N-Dekker commented Jul 4, 2018

See: Fraunhofer MEVIS Forum » MeVisLab » Feature Requests » Proposal to ease creating MeVisLab fields in C++

@N-Dekker
Copy link
Author

N-Dekker commented Jul 6, 2018

FieldCreator aims to ease initializing MeVisLab fields in C++ . It has a static create function which specifies how to create a field:

FieldCreator::create(container, name, initialValue, fieldFunction)

Supporting the following arguments:

  • container: a reference to the FieldContainer to which the new field will be added
  • name: the name of the new field
  • initialValue: an optional argument, which will be passed to field.setValue.
  • fieldFunction: an optional function pointer or function object, to allow specifying further post-initialization, just before the field is added to the container.

It allows initializing the most common types of fields, for example as follows:

CustomModule::CustomModule()
:
Module{ numberOfInputImages, numberOfOutputImages },
_intFld( FieldCreator::create(*getFieldContainer(), "Number", 42) ),
_doubleFld( FieldCreator::create(*getFieldContainer(), "Double") ),
_boolFld( FieldCreator::create(*getFieldContainer(), "Boolean", true) ),
_notifyFld( FieldCreator::create(*getFieldContainer(), "Notify") ),
_inputBaseFld( FieldCreator::create(*getFieldContainer(), "Input") ),
_outputBaseFld( FieldCreator::create(*getFieldContainer(), "Output", nullptr, [](Field& fld){ fld.setOutputOnly(); }) )
{
}

Note that in this case there is no need to call handleNotificationOff() within the custom module constructor. Each field is added to the FieldContainer after its initialization, and after its value is set, to avoid notifications to the module while the field is not yet ready.

The FieldCreator class is designed to allow a module class to have a reference, rather than a pointer, for each Field object it contains. (As the ISO C++ FAQ says: "Use references when you can, and pointers when you have to.") In many cases, this can actually be a const reference to the field.

FieldCreator::create returns a proxy, which is implicitly converted to the right field type. FieldCreator::create is overloaded to support ml::TypedEnumField as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment