🌍 Languages/语言: English | 简体中文
This is a lightweight, cross-platform, efficient and header-only INI configuration parsing library designed for C++ projects. It provides a concise, intuitive and elegant API, supports parsing, modifying and writing INI configuration information from files, std::istream or std::string, and has line-level comment retention function to ensure that the original comments are not lost, making configuration management easier and more efficient.
There are countless INI parsing libraries, and each library even has its own reason for existence. Our design philosophy is:
- Ultimate simplicity, out-of-the-box: Header-only, zero dependencies, only C++11, easy to integrate any project.
- Focus on experience, close to the practical: support for annotation retention, automatic type conversion, custom parsing, focus on improving the developer's daily experience.
- Cross-platform, Maintainable, Reliable: Compatible with mainstream platforms, combined with unit testing and memory checking to ensure long-term stable operation.
If you are looking for a simple, functional, elegant, stable modern C++ solution, then this library is for you. If you know how to use std::map, you are ready.
- Lightweight & Dependency-Free: Only relies on the C++11 standard library, no additional dependencies required.
- Easy Integration: Header-only design, out of the box, simple enough.
- Intuitive API: Provides a clear and user-friendly interface to simplify INI file operations.
- Cross-platform: Supports Linux, Windows, MacOS and other systems, as well as mainstream compilers.
- Multiple Data Sources: Supports parsing INI data from files, std::stringorstd::istream, and writing to them.
- Automatic Type Conversion: Supports multiple data types and can automatically handle type conversion (elegant API interface).
- Support comment function: Support ini line comments (;or#), you can add line comments for[section]andkey=value(does not support end-of-line comments).
- Custom type conversion: You can customize type conversion, inifile will automatically convert according to the definition you wrote (reduce duplication)
- Support case insensitivity: Provides optional case insensitivity (for sectionandkey)
- Fully tested and memory-safe: Functionality has been verified with the Catch2 unit testing framework and memory management is leak-free with Valgrind.
Ideal for C++ projects that require parsing, editing, and storing INI configuration files. The following is the basic ini format:
; comment
[section]
key = valueNote: The library uses
std::stringto encapsulate filed values internally, which is very compatible withUTF-8encoding, but other encoding specifics may be different.
Method 1: Header-only
- Copy the inifile.hheader file to your project folder.
- Include it in your source code using #include "inifile.h".
Method 2: Using CMake
- 
Create an inifilefolder in your project (name can be customized).
- 
Copy all contents from the includefolder of this project into theinifilefolder.
- 
Add the following line to your main CMakeLists.txtfile:add_subdirectory(inifile) # "inifile" is the folder name created in step 1 target_link_libraries(<target_name> PRIVATE inifile) # link inifile 
- 
Include it in your source code using #include <inifile/inifile.h>.
Below are some simple usage examples. For more details, refer to the./examples/ folder.
#include "inifile.h"
int main()
{
  constexpr char path[] = "path/to/ini/file";
  ini::inifile inif;
  inif["section"]["key0"] = true;
  inif["section"]["key1"] = 3.14159;
  inif["section"]["key2"] = "value";
  // Save the ini file, returns whether the save was successful or not.
  bool isok = inif.save(path);
}#include "inifile.h"
int main()
{
  constexpr char path[] = "path/to/ini/file";
  ini::inifile inif;
  // Load the ini file, return whether the loading was successful or not.
  bool isok = inif.load(path);
  bool        b = inif["section"]["key0"];
  double      d = inif["section"]["key1"];
  std::string s = inif["section"]["key2"];
  // Explicit type conversion via as function
  bool        bb = inif["section"]["key0"].as<bool>();
  double      dd = inif["section"]["key1"].as<double>();
  std::string ss = inif["section"]["key2"].as<std::string>();
  
  bool bbb;
  double ddd;
  std::string sss;
  inif["section"]["key0"].as_to(bbb);  // Explicit type conversion
  inif["section"]["key1"].as_to(ddd);  // Explicit type conversion
  inif["section"]["key2"].as_to(sss);  // Explicit type conversion
}#include "inifile.h"
int main()
{
  // Create istream object "is" ...
  ini::inifile inif;
  inif.read(is);
}#include "inifile.h"
int main()
{
  // Create ostream object "os" ...
  ini::inifile inif;
  inif.write(os);
}#include "inifile.h"
int main()
{
  // Create string object "s" ...
  ini::inifile inif;
  inif.from_string(s);
}#include "inifile.h"
int main()
{
  ini::inifile inif;
  inif["section"]["key"] = "value";
  std::string s = inif.to_string();
}Explanation: If the section or key does not exist, The operator[] and set functions will directly insert the section key, and if the section key exists, update the field value.
#include "inifile.h"
int main()
{
  ini::inifile inif;
    
  /// Setting a single value
  inif["section"]["key1"] = "value";
  inif["section"]["key2"].set("hello");
  inif.set("section", "key3", true);
  inif["section"].set("key4", 3.14159);
    
  /// Setting multiple values for the same section, supporting different types
  inif["section2"].set({{"bool", false},
                        {"int", 123},
                        {"double", 999.888},
                        {"string", "ABC"},
                        {"char", 'm'}});
}Explanation: There are two things to keep in mind when getting values.
- It is important to know whether a given section key exists or not, and different calling functions will have different strategies for handling this;
- Use operator[]to return reference. If the given section or key does not exist, will insert an empty field value and set the field to an empty string (Behavior similar tostd::map's[]).
- Use the get()function to return the value. If the given section or key does not exist, field will not be inserted, but a default empty field value (default value can be specified) will be returned.
- Use the at()function to return a reference, and if the given section or key does not exist, throw an exception:std::out_of_range
 
- Use 
- Whether you can perform type automatic conversion, the above three functions return the ini::fieldwrapper object, if the object to other types should be noted:- Whether the type conversion is allowed, if the type conversion is not allowed, an exception is thrown: std::invalid_argument, (e.g. converting"abc"toint).
- Whether the range of numeric type conversion overflows, throwing an exception if it is outside the range of the target type: std::out_of_range, (e.g. convertingINT_MAXtouint8_t).
 
- Whether the type conversion is allowed, if the type conversion is not allowed, an exception is thrown: 
#include "inifile.h"
int main()
{
  ini::inifile inif;
  try
  {
    /// automatic type conversion
    std::string s0 = inif["section"]["key1"];
    bool isok = inif["section"]["key2"];
    int ii0 = inif.get("section", "key3");
    int ii2 = inif.get("section", "key3", -1); // Specify default values
    std::string ss2 = inif["section"].get("key4");
    std::string ss3 = inif["section"].get("key5", "default"); // Specify default values
    double dd0 = inif.at("section").at("key");
    std::cout << "section-key:" << inif["section"]["key"].as<double>() << std::endl;
  }
  catch (const std::exception &e)
  {
    std::cout << e.what() << std::endl;
  }
}This library supports setting line-level comments for [section] and key=value (end-of-line comments are not supported), and the comment symbols can be ; and #; it can also retain the comment content from the data source. View example
The following methods are fully functionally equivalent, and which one you choose depends largely on your style preference:
| Short form | Full form | Description | 
|---|---|---|
| f.set_comment(...) | f.comment().set(...) | Set comment, override original comment (supports multiple lines) | 
| f.add_comment(...) | f.comment().add(...) | Append comments (supports multiple lines) | 
| f.clear_comment() | f.comment().clear() | Clear all comments | 
#include "inifile.h"
int main()
{
  ini::inifile inif;
  // Set value
  inif["section"]["key0"] = true;
  inif["section"]["key1"] = 3.141592;
  inif["section"]["key2"] = "value";
  // Set comments if necessary
  inif["section"].set_comment("This is a section comment.");                     // set section comment, Overwrite Mode
  inif["section"]["key1"].set_comment("This is a key-value pairs comment", '#'); // set key=value pairs comment
	
  // Clear section comment
  inif["section"].clear_comment();
  
  // Add section comment
  inif["section"].add_comment("section comment01");                    // add section comment, Append Mode
  inif["section"].add_comment("section comment02\nsection comment03"); // Multi-line comments are allowed, lines separated by `\n`
  
  // Get the comment object reference
  ini::comment &cmt = inif["section"]["key"].comment();  // get reference to comment
  
  // Read comment content
  const std::vector<std::string> &view = cmt.view();  // view (non-owning)
  
  bool isok = inif.save("config.ini");
}The contents of config.ini should be:
; section comment01
; section comment02
; section comment03
[section]
key0=true
# This is a key-value pairs comment
key1=3.141592
key2=valueThis library supports case insensitivity for section and key, use ini::case_insensitive_inifile, please View example
#include "inifile.h"
int main()
{
  const char *str = R"(
    [Section]
    KEY=Value
    Flag=123
    hello=world
  )";
  
  ini::case_insensitive_inifile inif;  // Create a case-insensitive INI file object
  inif.from_string(str);               // Read INI data from string
  // Test case-insensitive section and key access
  std::cout << "inif.contains(\"Section\") = " << inif.contains("Section") << std::endl;  // true
  std::cout << "inif.contains(\"SECTION\") = " << inif.contains("SECTION") << std::endl;  // true
  std::cout << "inif.contains(\"SeCtIoN\") = " << inif.contains("SeCtIoN") << std::endl;  // true
  std::cout << "inif.at(\"section\").contains(\"key\") = " << inif.at("section").contains("key") << std::endl;
  std::cout << "inif.at(\"section\").contains(\"Key\") = " << inif.at("section").contains("Key") << std::endl;
  std::cout << "inif.at(\"SECTION\").contains(\"KEY\") = " << inif.at("SECTION").contains("KEY") << std::endl;
  std::cout << "inif.at(\"SECTION\").contains(\"flag\") = " << inif.at("SECTION").contains("flag") << std::endl;
  std::cout << "inif.at(\"SECTION\").contains(\"FLAG\") = " << inif.at("SECTION").contains("FLAG") << std::endl;
  return 0;
}Automatic type conversion works on ini::field objects, allowing ini::field <=> other type to be converted to and from each other; but be careful:  An exception will be thrown if the conversion fails.
#include "inifile.h"
int main()
{
  /// other type -> ini::field
  ini::field f(true);
  ini::field f1(10);
  ini::field f2 = 3.14;
  ini::field f3 = 'c';
  ini::field f4 = "abc";
  
  /// ini::field -> other type
  bool        b = f;
  int         i = f1;
  double      d = f2;
  char        c = f3;
  std::string s = f4;
  
  ini::inifile inif;
  inif["section"]["key"] = true; // bool -> ini::field
  
  /// Get direct type(ini::field)
  auto val = inif["section"]["key"]; // val type is ini::field
  ini::field val2 = inif["section"]["key"]; 
  
  /// explicit type conversion
  bool bb = inif["section"]["key"].as<bool>();
    
  /// automatic type conversion
  bool bb2 = inif["section"]["key"];
  
  /// Type conversion failure throws an exception
  double n = inif["section"]["key"]; // error: Converting true to double is not allowed.
}Supported types for automatic conversions:
- bool
- char,- signed char,- unsigned char
- short,- unsigned short
- int,- unsigned int
- long,- unsigned long
- long long,- unsigned long long
- float
- double
- long double
- std::string
- const char *
- std::string_view(C++17)
Q: Can user-defined types be automatically converted like the basic data types mentioned above?
A: Yes, you can. You only need to follow the following rules to customize the type conversion so that inifile can automatically handle user-defined types.
You can provide a special template class for automatic type conversion for user-defined types, which allows the inifile library to automatically convert according to the rules you define, so that it can store custom classes in ini fields, which can greatly reduce code duplication. The following are custom rules and templates:
- 
Use the INIFILE_TYPE_CONVERTERmacro to specialize the custom type (Must have a default constructor);
- 
Define static void encodefunction, which is used to define how to convert custom types into ini storage strings (the storage string cannot contain newlines);
- 
Define static void decodefunction, which is used to define how to convert the ini storage string into a custom type;
/// Specialized type conversion template
template <>
struct INIFILE_TYPE_CONVERTER<CustomClass>  // User-defined type `CustomClass`
{
  static void encode(const CustomClass &obj, std::string &value)  // pass by reference
  {
    // Convert the CustomClass object `obj` to ini storage string `value`
  }
  static void decode(const std::string &value, CustomClass &obj)  // must be static
  {
    // Convert the ini storage string `value` to a CustomClass object `obj`
  }
}To facilitate writing
encodeanddecodefunctions, this library providesini::join,ini::split()andini::trim()utility functions
Example 1: The following is an example of converting a user-defined class Person object to an ini field.   View example
/// @brief User-defined classes
struct Person
{
  Person() = default;  // Must have a default constructor
  Person(int id, int age, const std::string &name) : id(id), age(age), name(name) {}
  int id = 0;
  int age = 0;
  std::string name;
};
/// @brief Custom type conversion (Use INIFILE_TYPE_CONVERTER to specialize Person)
template <>
struct INIFILE_TYPE_CONVERTER<Person>
{
  // "Encode" the Person object into a value string
  static void encode(const Person &obj, std::string &value)
  {
    const char delimiter = ',';
    // Format: id,age,name; Note: Please do not include line breaks in the value string
    value = std::to_string(obj.id) + delimiter + std::to_string(obj.age) + delimiter + obj.name;
  }
  // "Decode" the value string into a Person object
  static void decode(const std::string &value, Person &obj)
  {
    const char delimiter = ',';
    std::vector<std::string> info = ini::split(value, delimiter);
    // Exception handling can be added
    obj.id = std::stoi(info[0]);
    obj.age = std::stoi(info[1]);
    obj.name = info[2];
  }
};
int main()
{
  ini::inifile inif;
  Person p = Person{123456, 18, "abin"};
  inif["section"]["key"] = p;          // set person object
  Person pp = inif["section"]["key"];  // get person object
}Example 2: You can nest calls to INIFILE_TYPE_CONVERTER<T> to achieve automatic conversion of STL containers, which can achieve the following effects of directly assigning or getting values from containers. For the specific implementation, please Click to view details
// Define vectors of different types
std::vector<int>         vec1 = {1, 2, 3, 4, 5};
std::vector<double>      vec2 = {1.1111, 2.2222, 3.3333, 4.4444, 5.5555};
std::vector<std::string> vec3 = {"aaa", "bbb", "ccc", "ddd", "eee"};
// Set different types of vectors in the INI file object
inif["section"]["key1"] = vec1;
inif["section"]["key2"] = vec2;
inif["section"]["key3"] = vec3;
// Get different vectors from INI file object
std::vector<int>         v1 = inif["section"]["key1"];
std::vector<double>      v2 = inif["section"]["key2"];
std::vector<std::string> v3 = inif["section"]["key3"];Provides a variety of other utility functions, including checking whether it is empty empty(), querying the total number size(), querying the number of keys count(), checking whether it contains elements contains(), finding elements find(), removing elements remove() and erase(), clearing all elements clear(), iterator access: begin(), end(), cbegin(), cend(), and supporting range base for loops. For details, please refer to the Common API Description.
Here is an iterator to access ini information:
#include "inifile.h"
int main()
{
  constexpr char path[] = "path/to/ini/file";
  ini::inifile inif;
  bool isok = inif.load(path);
  for (const auto &sec_pair : inif)
  {
    const std::string §ion_name = sec_pair.first;
    const ini::section §ion = sec_pair.second;
    std::cout << "  section '" << section_name << "' has " << section.size() << " key-values." << std::endl;
    for (const auto &kv : section)
    {
      const std::string &key = kv.first;
      const auto &value = kv.second;
      std::cout << "    kv: '" << key << "' = '" << value << "'" << std::endl;
    }
  }
}| Description | Link | 
|---|---|
| Basic usage of inifile | inifile_basic.cpp | 
| Case-insensitive inifileexample | inifile_case_insensitive.cpp | 
| Saving INI file with inifile | inifile_save.cpp | 
| Loading INI file with inifile | inifile_load.cpp | 
| Reading INI config from stream | inifile_read.cpp | 
| Writing INI config to stream | inifile_write.cpp | 
| Comment feature of inifile | inifile_comment.cpp | 
| Reading/writing user-defined types | inifile_custom.cpp | 
| Reading/writing user-defined types 2 | inifile_custom2.cpp | 
| Usage of iniutility functions | inifile_utility.cpp | 
| class name | description | 
|---|---|
| ini::inifile | Corresponds to the entire ini data, including all sections. | 
| ini::section | corresponds to the content of the entire section, which contains all the key-value values of the section. | 
| ini::case_insensitive_inifile | The sectionandkeyare case-insensitive; all other features are the same asini::inifile. | 
| ini::case_insensitive_section | The keyare case-insensitive; all other features are the same asini::section. | 
| ini::field | corresponds to the value field in the ini data, supports multiple data types, supports automatic type conversion. | 
| ini::comment | ini file comment class, manage section and key-value comments. | 
click to expand
| function name | function signature | function description | 
|---|---|---|
| comment | comment(const std::string &str, char symbol = ';') | Constructor | 
| comment | comment(const std::vector<std::string> &vec, char symbol = ';') | Constructor | 
| comment | comment(std::initializer_list<std::string> list, char symbol = ';') | Constructor | 
| swap | void swap(comment &other) noexcept | Swap Function | 
| empty | bool empty() const noexcept | Is it empty | 
| clear | void clear() noexcept | Clear Comments | 
| set | void set(const std::string &str, char symbol = ';') | Set Comment (Overwrite Mode) | 
| add | void add(const std::string &str, char symbol = ';') | Adding comments (append mode) | 
| view | const std::vector<std::string> &view() const | Returns the comment container constant reference | 
| to_vector | std::vector<std::string> to_vector() const | Returns a copy of the comment container | 
click to expand
The following functions will throw an exception if the type conversion fails or the value overflows:
| function name | function signature | function description | 
|---|---|---|
| field | field(const T &other) | Constructs a field object, converting a T type to a field value. | 
| set | field &set(const T &value) | Set field value, convert T type to field value. | 
| operator= | field &operator=(const T &rhs) | Set field value, convert T type to field value. | 
| operator T | operator T() const | Converting field types to T type | 
| as | T as() const | Converting field types to T type | 
| as_to | T &as_to(T &out) const | Convert the field type to the given T type object | 
| swap | void swap(field &other) noexcept | Swap Function | 
| set_comment | void set_comment(const std::string &str, char symbol = ';') | Set the key-value comment, overwrite mode | 
| add_comment | void add_comment(const std::string &str, char symbol = ';') | Add key-value comments, append mode | 
| clear_comment | void clear_comment() | Clear the comments of key-value | 
| comment | ini::comment &comment() | Get the comment content of key-value (reference) | 
click to expand
| function name | function signature | function description | 
|---|---|---|
| operator[] | field &operator[](std::string key) | Return an ini::field reference, or insert an empty ini::field if it doesn't exist. | 
| set | field &set(std::string key, T &&value) | Inserts or updates a field with the specified key | 
| contains | bool contains(std::string key) const | Determine if the key exists | 
| at | field &at(std::string key) | Returns a reference to the field value of the element with the specified key. Throws std::out_of_range exception if element does not exist | 
| swap | void swap(section &other) noexcept | Swap Function | 
| get | field get(std::string key, field default_value = field{}) const | Get the value (copy) of the key, if the key does not exist, then return the default value of default_value. | 
| find | iterator find(key_type key) | Find the iterator of the specified key value, if it does not exist, return the end iterator | 
| erase | iterator erase(iterator pos) | Delete the key-value pair of the specified iterator | 
| remove | bool remove(std::string key) | Removes the specified key-value key pair, or does nothing if it does not exist. | 
| empty | bool empty() const noexcept | Determine whether the key-value pair is empty, and return true if it is empty | 
| clear | void clear() noexcept | Clear all key-value pairs | 
| size | size_type size() const noexcept | Returns how many key-value pairs there are. | 
| keys | std::vector<key_type> keys() const | Get all keys in the section. | 
| values | std::vector<ini::filed> values() const | Get all values in the section.each value is a ini::fieldobject. | 
| items | std::vector<value_type> items() const | Get all key-value pairs in the section. | 
| count | size_type count(const key_type &key) const | Returns the number of key-value pairs for the specified key. | 
| begin | iterator begin() noexcept | Returns the begin iterator. | 
| end | iterator end() noexcept | Returns the end iterator. | 
| set_comment | void set_comment(const std::string &str, char symbol = ';') | Set section comment, overwrite mode, comment string allows line breaks \n | 
| add_comment | void add_comment(const std::string &str, char symbol = ';') | Add section comments, append mode, comment string allows line breaks \n | 
| clear_comment | void clear_comment() | Clear section comments | 
| comment | ini::comment &comment() | Get the comment content of section(reference) | 
click to expand
| function name | function signature | function description | 
|---|---|---|
| operator[] | section &operator[](std::string sec) | Returns a reference to ini::section, or inserts an empty ini::section if it does not exist. | 
| set | field &set(std::string sec, std::string key, T &&value) | Set section key-value | 
| contains | bool contains(std::string section) const | Determines if the specified section exists. | 
| contains | bool contains(std::string section, std::string key) const | Determines if the specified key exists in the specified section. | 
| at | section &at(std::string section) | Returns a reference to the specified section. If no such element exists, an exception of type std::out_of_range is thrown | 
| swap | void swap(inifile &other) noexcept | Swap Function | 
| get | field get(std::string sec, std::string key, field default_value = field{}) const | Returns the field value of the specified key for the specified section, or the default value default_value if section or key does not exist | 
| find | iterator find(key_type key) | Find the iterator of the specified section, if it does not exist, return the end iterator | 
| erase | iterator erase(iterator pos) | Deletes the section of the specified iterator (including all its elements) | 
| remove | bool remove(std::string sec) | Removes the specified section (including all its elements). | 
| empty | bool empty() const noexcept | Determines whether there is no section, returns true if there is no section | 
| clear | void clear() noexcept | Clear all sections | 
| size | size_type size() const noexcept | Returns how many sections | 
| sections | std::vector<key_type> sections() const | Get all section names in the INI file. | 
| count | size_type count(key_type key) const | Returns how many sections have the specified section-name | 
| begin | iterator begin() noexcept | Returns the begin iterator. | 
| end | iterator end() noexcept | Returns the end iterator. | 
| read | void read(std::istream &is) | Read ini information from istream | 
| write | void write(std::ostream &os) const | Writes ini information to ostream | 
| from_string | void from_string(const std::string &str) | Read ini information from string | 
| to_string | std::string to_string() const | Converts the inifile object to the corresponding string | 
| load | bool load(const std::string &filename) | Load ini information from ini file, return whether it was successful or not | 
| save | bool save(const std::string &filename) | Save ini information to an ini file, return whether it was successful or not | 
This project uses CMake and requires C++11 or later. Please ensure the following dependencies are installed:
- Git
- CMake version ≥ 3.20
- A C++11-compatible compiler (e.g., GCC, Clang, or MSVC)
Step 1: Clone the Repository
git clone --recurse-submodules https://github.com/abin-z/IniFile.gitIf you already cloned the repository without
--recurse-submodules, run the following to initialize submodules:git submodule update --init --recursive
Step 2: Build the Project
cmake -B build         # Generate the project
cmake --build build    # Build the projectStep 3: Run
./initest     # Run unit testsWe welcome contributions! Feel free to submit Issues and Pull Requests to improve this project.
- Contributions are not limited to code: documentation, tests, performance improvements, and bug reports are also valuable.
- Ensure all unit tests pass (test_inifile.cpp) before submitting.
- Run clang-formatfor code formatting (.clang-format).
Thank you Catch2 for providing strong support and assisting in the unit testing of this project!
Thanks to Valgrind for his help in securing memory and preventing memory leaks!
This project is licensed under the MIT License.
Copyright © 2025–Present Abin.